nativeloop
Version:
⭐ Axway Amplify module for using nativeloop with Appcelerator Titanium SDK Framework
493 lines (450 loc) • 15.5 kB
JavaScript
;
var Promise = require( "bluebird" );
const fs = Promise.promisifyAll( require( "fs-extra" ) );
const path = require( "path" );
const chalk = require( "chalk" );
const _ = require( 'lodash' );
// var conf = require('rc')('nativeloop', {});
const pathExists = require( 'path-exists' );
const npm = require( '@geek/npm' );
const temp = require( "temp" );
const module_name = path.parse( module.id ).name;
// const debug = require('debug')('nativeloop');
const spinner = new( require( '@geek/spinner' ) )();
const figures = require( 'figures' );
const findit = require( 'findit' );
const using = Promise.using;
// debug logger
var logger = ( func_name ) => {
var prefix = func_name ? `[${module_name}.${func_name}] ` : `[${module_name}`;
return _.wrap( require( 'debug' )( 'nativeloop' ), ( func, msg ) => func( chalk.blue( prefix ) + msg ) );
}
var debug = logger();
// exports.command = 'create <App Name> [--path <Directory>] [--id <App ID>] [--template <Template Name>]'
exports.desc = 'Creates a new project for native development with {nativeloop}.'
var required = ( prop, value ) => {
if ( value ) {
// console.error( 'you are here → value' );
return value;
} else {
// console.error( 'you are here → error' );
throw new Error( prop + " is required." )
}
};
var builder = ( yargs ) => {
yargs.options( {
'template': {
alias: 't',
default: '@nativeloop/template-default',
describe: 'Template to use for creating your new app',
demand: false,
type: 'string',
},
'name': {
alias: 'n',
default: yargs.argv._[ 1 ],
coerce: ( value ) => required( 'name', value ),
describe: 'Name of your project',
demand: false,
type: 'string'
},
'id': {
//The bundle ID string must be a uniform type identifier (UTI) that contains only alphanumeric
//characters (A-Z,a-z,0-9), hyphen (-), and period (.)
alias: 'i',
describe: 'ID for your project',
demand: false,
type: 'string'
},
'publisher': {
default: 'my-company',
describe: 'Publisher for your project',
demand: false,
type: 'string'
},
'copyright': {
alias: 'c',
default: '',
describe: 'Copyright for your project',
demand: false,
type: 'string'
},
'description': {
alias: 'd',
default: 'Another awesome nativeloop app!',
describe: 'Description for your project',
demand: false,
type: 'string'
},
'guid': {
alias: 'g',
default: '00000000-0000-0000-0000-000000000000',
describe: 'Guid for your project',
demand: false,
type: 'string'
},
'url': {
alias: 'u',
default: '',
describe: 'Url for your project',
demand: false,
type: 'string'
},
'path': {
alias: 'p',
describe: 'Specifies the directory where you want to initialize the project, if different from the current directory. The directory must already exist.',
demand: false,
type: 'string'
},
'force': {
alias: 'f',
describe: 'If set, applies the default project configuration and does not show the interactive prompt. ',
demand: false,
type: 'string'
}
} );
// Very weird.... I need to do this in order to get yargs to complete properly.
JSON.stringify( yargs, null, 2 );
return yargs;
}
var execute = ( argv ) => {
let debug = logger( 'execute' );
var appc_directory;
debug( "argv: " + JSON.stringify( argv, null, 2 ) );
debug( "__dirname: " + __dirname );
debug( "process.cwd(): " + process.cwd() );
var project_directory = argv[ "path" ];
debug( "project_directory: " + project_directory );
debug( "project_directory.exists: " + pathExists.sync( project_directory ) );
var getTempDirectory = function () {
const temp_directory = temp.path( {
prefix: "nativeloop"
} );
fs.emptyDirSync( temp_directory );
// const nodeModulesDir = path.join(temp_directory, "node_modules");
debug( 'temp_directory: ' + JSON.stringify( temp_directory, null, 2 ) );
return Promise.resolve( temp_directory )
.disposer( ( directory, promise ) => {
fs.removeSync( directory );
} );
}
var configure_project_directory = function () {
spinner.start( "Configuring project directory" );
return fs.ensureDirAsync( project_directory )
.then( () => {
spinner.succeed();
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( project_directory ) );
spinner.column -= 4;
} );
}
var template_appc = function ( filename ) {
let debug = logger( 'template_appc' );
var filename = path.join( appc_directory, filename );
// debug("templating file: " + filename);
spinner.start( filename );
return fs.readFileAsync( filename )
.then( ( source ) => fs.writeFileAsync( filename, _.template( source )( argv ) ) )
.then( () => spinner.succeed() );
}
var template_nativeloop = function ( filename ) {
// let debug = logger('template_nativeloop');
var filename = path.join( project_directory, filename );
// debug("templating file: " + filename);
spinner.start( filename );
return fs.readFileAsync( filename )
.then( ( source ) => fs.writeFileAsync( filename, _.template( source )( argv ) ) )
.then( () => spinner.succeed() );
}
var template_files = function () {
// let debug = logger('template_files');
spinner.stopAndPersist( figures.arrowRight, "Templating files" );
spinner.column += 4;
return Promise.all( _.map( [ 'tiapp.xml' ], template_appc ) )
.then( () => Promise.all( _.map( [ 'readme.md' ], template_nativeloop ) ) )
.then( () => {
spinner.column -= 4;
return true;
} );
}
var findTiappXml = function ( root ) {
let debug = logger( 'findTiappXml' );
return new Promise( ( resolve, reject ) => {
spinner.start( 'Looking for Appcelerator project folder' );
debug( 'looking for tiapp.xml in: ' + root );
var finder = findit( root );
finder.on( 'file', function ( file, stat ) {
var filepath = path.parse( file );
if ( filepath.base === 'tiapp.xml' ) {
spinner.succeed();
resolve( filepath.dir );
finder.stop();
}
} );
} )
.then( result => {
appc_directory = result;
if ( !appc_directory ) {
spinner.fail();
spinner.column += 4;
spinner.fail( chalk.red( 'Appcelerator directory not found' ) );
spinner.column -= 4;
return false;
}
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( appc_directory ) );
spinner.column -= 4;
// debug("appc_directory: " + appc_directory);;
} );
}
/**
* Installs nativeloop_mobile module
* @function install_nativeloop_module
* @since 1.0.0
*/
var install_nativeloop_mobile = function () {
let debug = logger( 'install_nativeloop_mobile' );
debug( 'installing nativeloop mobile' );
return pathExists( path.join( project_directory, 'node_modules', '@nativeloop', 'mobile' ) )
.then( exists => {
spinner.start( 'Installing @nativeloop/mobile' );
if ( exists ) {
debug( 'skipping installation of @nativeloop/mobile: directory already exists' );
spinner.text += chalk.gray( ' [skipped]' );
spinner.stopAndPersist( chalk.gray( figures.cross ) );
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( 'Directory already exists' ) );
spinner.column -= 4;
return true;
} else {
debug( 'installing @nativeloop/mobile in directory: ' + project_directory );
return npm.install( [ '@nativeloop/mobile' ], {
cwd: project_directory,
silent: true,
} )
.then( () => spinner.succeed() );
}
} );
}
/**
* Installs nativeloop widget
* @function install_nativeloop_widget
* @since 1.0.0
*/
var install_nativeloop_widget = function () {
let debug = logger( 'install_nativeloop_mobile' );
debug( 'installing nativeloop mobile' );
return pathExists( path.join( project_directory, 'node_modules', 'alloy-widget-nativeloop' ) )
.then( exists => {
spinner.start( 'Installing alloy-widget-nativeloop' );
if ( exists ) {
debug( 'skipping installation of alloy-widget-nativeloop: directory already exists' );
spinner.text += chalk.gray( " [skipped]" );
spinner.stopAndPersist( chalk.gray( figures.cross ) );
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( 'Directory already exists' ) );
spinner.column -= 4;
return true;
} else {
debug( 'installing alloy-widget-nativeloop in directory: ' + project_directory );
return npm.install( [ 'alloy-widget-nativeloop' ], {
cwd: project_directory,
silent: true,
} )
.then( () => spinner.succeed() );
}
} );
}
// var cleanup = function() {
// let debug = logger('cleanup');
// spinner.start("Cleaning up temporary files");
// return fs.removeAsync(temp_directory)
// .then(() => spinner.succeed())
// .catch(err => {
// spinner.fail();
// debug("Error cleaning up: " + err.message || err);
// spinner.column += 4;
// spinner.fail(err);
// spinner.column -= 4;
// });
// }
var configure_project_files = function () {
spinner.stopAndPersist( figures.arrowRight, 'Configuring project files' );
spinner.column += 4;
debug( 'renaming template.json file: ' + project_directory );
spinner.start( 'Renaming template.json' );
return pathExists( path.join( project_directory, "template.json" ) )
.then( ( exists ) => {
if ( !exists ) {
debug( "skipping rename of template.json: file does not exist" );
spinner.text += chalk.gray( " [skipped]" );
spinner.stopAndPersist( chalk.gray( figures.cross ) );
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( "File does not exist" ) );
spinner.column -= 4;
return true;
} else {
return fs.copyAsync( path.join( project_directory, "template.json" ), path.join( project_directory, "package.json" ), {
clobber: true
} )
.then( () => fs.removeAsync( path.join( project_directory, "template.json" ) ) )
.then( () => spinner.succeed() );
}
} )
.then( () => {
debug( 'renaming template.md file: ' + project_directory );
spinner.start( "Renaming template.md" );
return pathExists( path.join( project_directory, "template.md" ) )
.then( ( exists ) => {
if ( !exists ) {
debug( "skipping rename of template.md: file does not exist" );
spinner.text += chalk.gray( " [skipped]" );
spinner.stopAndPersist( chalk.gray( figures.cross ) );
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( "File does not exist" ) );
spinner.column -= 4;
return true;
} else {
return fs.copyAsync( path.join( project_directory, "template.md" ), path.join( project_directory, "readme.md" ), {
clobber: true
} )
.then( () => fs.removeAsync( path.join( project_directory, "template.md" ) ) )
.then( () => spinner.succeed() )
}
} )
} )
.then( () => {
spinner.start( "Configuring package.json" );
var pkg = _.defaults( {
name: argv.name,
description: argv.description,
author: {
name: argv.publisher
},
version: "1.0.0-revision.0"
}, _.omitBy( require( path.join( project_directory, "package.json" ) ), ( value, key ) => {
return _.startsWith( key, "_" ) || ( _.includes( [ "gitHead", "readme" ], key ) );
} ) );
return fs.writeJsonAsync( path.join( project_directory, "package.json" ), pkg )
.then( () => spinner.succeed() )
} )
.then( () => {
spinner.column -= 4;
return true;
} );
}
var copy_template = function ( name ) {
let debug = logger( 'copy_template' );
spinner.stopAndPersist( figures.arrowRight, "Installing template" );
var source = path.resolve( name );
debug( "source: " + source );
spinner.column += 4;
spinner.start( "Checking for local template" );
return pathExists( source )
.then( exists => {
debug( "pathExists.sync(source): " + exists );
if ( exists ) {
spinner.succeed();
debug( 'copying files to project directory: ' + project_directory );
spinner.start( 'Copying template to project folder' );
return fs.copyAsync( source, project_directory, {
clobber: true
} )
.then( () => {
spinner.succeed();
spinner.column -= 4;
return true;
} );
} else {
spinner.text += chalk.gray( " [skipped]" );
spinner.stopAndPersist( chalk.gray( figures.cross ) );
spinner.column += 4;
spinner.stopAndPersist( figures.pointerSmall, chalk.gray( "Local template not found." ) );
spinner.column -= 4;
return using( getTempDirectory(), ( temp_directory ) => {
const nodeModulesDir = path.join( temp_directory, "node_modules" );
debug( 'installing remote template to: ' + project_directory );
spinner.start( 'Installing remote template: ' + chalk.gray( name ) );
return npm.install( [ name, '--ignore-scripts', '--global-style' ], {
cwd: temp_directory,
silent: true,
} )
.then( () => {
return new Promise( ( resolve, reject ) => {
spinner.succeed();
spinner.start( "Examining template" );
var finder = findit( nodeModulesDir );
finder.on( 'file', function ( file, stat ) {
var filepath = path.parse( file );
if ( _.includes( [ "package.json", "template.json" ] ), filepath.base ) {
spinner.succeed();
resolve( filepath.dir );
finder.stop();
}
} );
} );
} )
.then( ( template_source ) => {
debug( 'copying files to project directory: ' + project_directory );
spinner.start( 'Copying template to project folder' );
return fs.copyAsync( template_source, project_directory, {
clobber: true
} )
.then( () => {
spinner.succeed();
spinner.column -= 4;
return true;
} );
} );
} )
}
} );
}
configure_project_directory()
.then( () => copy_template( argv.template ) )
.then( () => findTiappXml( project_directory ) )
.then( () => configure_project_files() )
.then( () => {
spinner.start( 'Installing npm dependencies' );
return npm.install( {
cwd: project_directory,
silent: true,
} )
.then( () => spinner.succeed() );
} )
// .then( () => install_nativeloop_mobile() )
// .then( () => {
// spinner.start( 'Running npm dedupe' );
// return npm.dedupe( {
// cwd: project_directory
// } )
// .then( () => spinner.succeed() );
// } )
.then( () => template_files() )
// .finally(() => {
// //TODO: Figure out why the last spinner entry is output twice
// cleanup();
// // spinner.stop();
// })
.catch( err => {
console.error( 'Error occurred: ' + err );
spinner.fail();
spinner.column += 4;
spinner.fail( err );
spinner.column -= 4;
} );
process.on( 'unhandledRejection', function ( reason, promise ) {
// console.error("Error occurred: " + reason);
spinner.fail();
spinner.column += 4;
spinner.fail( reason );
spinner.column -= 4;
} );
}
var handler = ( argv ) => {
argv.id = argv.id || _.kebabCase( argv.publisher.trim() ).toLowerCase() + "." + _.kebabCase( argv.name.trim() ).toLowerCase();
argv.path = argv.path || path.join( process.cwd(), argv.name );
execute( argv );
}
exports.handler = handler;
exports.builder = builder;