diff --git a/app/index.js b/app/index.js index fbd0651..b15ba5a 100644 --- a/app/index.js +++ b/app/index.js @@ -50,16 +50,16 @@ module.exports = class extends Generator { this.skipPrompt = true; let bluemix_ok= this._sanitizeOption(this.options, OPTION_BLUEMIX); let spec_ok= this._sanitizeOption(this.options, OPTION_SPEC); - if ( ! (bluemix_ok || spec_ok )) throw ("Must specify either bluemix or spec parameter"); - - if ( typeof this.options.bluemix.quiet == "undefined" || ! this.options.bluemix.quiet ) { + if ( ! (bluemix_ok || spec_ok )) throw ("Must specify either bluemix or spec parameter"); + + if ( typeof this.options.bluemix.quiet == "undefined" || ! this.options.bluemix.quiet ) { logger.info("Package info ::", Bundle.name, Bundle.version); - } - + } + let appName = this.options.bluemix.name || this.options.spec.appname; this.options.sanitizedAppName = this._sanitizeAppName(appName); - this.options.genSwagger= false; - this.options.openApiFileType= "yaml"; // default + this.options.genSwagger= false; + this.options.openApiFileType= "yaml"; // default this.options.parsedSwagger = undefined; let formatters = { @@ -75,8 +75,8 @@ module.exports = class extends Generator { .then(response => { this.options.loadedApi = response.loaded; this.options.parsedSwagger = response.parsed; - this.options.openApiFileType= response.type; - this.options.genSwagger= true; + this.options.openApiFileType= response.type; + this.options.genSwagger= true; }) .catch(err => { err.message = 'failed to parse document from bluemix.openApiServers ' + err.message; @@ -84,9 +84,9 @@ module.exports = class extends Generator { }) } - // micro service always gets swagger ui and no public + // micro service always gets swagger ui and no public if(this.options.spec && this.options.spec.applicationType === 'MS') { - this.options.genSwagger= true; + this.options.genSwagger= true; } } @@ -115,22 +115,23 @@ module.exports = class extends Generator { this.fs.copyTpl(this.templatePath('Dockerfile-tools'), this.destinationPath('Dockerfile-tools'), this.options); this.fs.copyTpl(this.templatePath('package.json'), this.destinationPath('package.json'), this.options); this.fs.copyTpl(this.templatePath('README.md'), this.destinationPath('README.md'), this.options); + this.fs.copyTpl(this.templatePath('idt.js'), this.destinationPath('idt.js'), this.options); - // if project will have swagger doc, ensure swagger ui and api route + // if project will have swagger doc, ensure swagger ui and api route if ( this.options.genSwagger ) { this.fs.copy(this.templatePath('public/swagger-ui'), this.destinationPath('public/swagger-ui')); - // if open api doc provided, write it else write default + // if open api doc provided, write it else write default if ( this.options.loadedApi ) { let yaml= this.options.bluemix.openApiServers[0].spec; //this.fs.writeJSON('public/swagger.'+this.options.openApiFileType, this.options.loadedApi); - this.fs.write('public/swagger.'+this.options.openApiFileType, yaml); - } + this.fs.write('public/swagger.'+this.options.openApiFileType, yaml); + } else { this.fs.copyTpl(this.templatePath('public/swagger.yaml'), this.destinationPath('public/swagger.yaml'), this.options); } } - else { + else { this.fs.delete(this.destinationPath('server/routers/swagger.js')); } @@ -138,11 +139,11 @@ module.exports = class extends Generator { if( this.options.genSwagger ) { this.fs.delete(this.destinationPath('server/routers/public.js')); } - else { + else { this.fs.copy(this.templatePath('public/index.html'), this.destinationPath('public/index.html')); } - // blank project is stripped down to bare minimum + // blank project is stripped down to bare minimum if(this.options.spec && this.options.spec.applicationType === 'BLANK') { this.fs.delete(this.destinationPath('server/routers/health.js')); } @@ -153,12 +154,12 @@ module.exports = class extends Generator { return name.toLowerCase().replace(REGEX_LEADING_ALPHA, '').replace(REGEX_ALPHA_NUM, ''); } - // return true if 'sanitized', false if missing, exception if bad data + // return true if 'sanitized', false if missing, exception if bad data _sanitizeOption(options, name) { let optionValue = options[name]; if (!optionValue) { logger.error("Missing", name, "parameter"); - return false; + return false; } if (typeof optionValue === "string" && optionValue.indexOf("file:") === 0) { @@ -166,16 +167,16 @@ module.exports = class extends Generator { let filePath = this.destinationPath("./" + fileName); logger.info("Reading", name, "parameter from local file", filePath); this.options[name] = this.fs.readJSON(filePath); - return true; + return true; } try { this.options[name] = typeof(this.options[name]) === "string" ? JSON.parse(this.options[name]) : this.options[name]; - return true; + return true; } catch (e) { logger.error(e); throw name + " parameter is expected to be a valid stringified JSON object"; - } + } } -}; \ No newline at end of file +}; diff --git a/app/templates/README.md b/app/templates/README.md index cc8895d..758b9b3 100644 --- a/app/templates/README.md +++ b/app/templates/README.md @@ -5,8 +5,22 @@ A generated Bluemix application [![](https://img.shields.io/badge/bluemix-powered-blue.svg)](https://bluemix.net) ## Run locally as Node.js application + ```bash npm install npm test npm start -``` \ No newline at end of file +``` + +## Build, run, and deploy using IDT + +```bash +# Install needed dependencies: +npm run idt:install +# Build the docker image for your app: +npm run idt:build +# Run the app locally through docker: +npm run idt:run +# Deploy your app to IBM Cloud: +npm run idt:deploy +``` diff --git a/app/templates/idt.js b/app/templates/idt.js new file mode 100644 index 0000000..162a99d --- /dev/null +++ b/app/templates/idt.js @@ -0,0 +1,89 @@ +'use strict' + +/* + * Wrapper for the idt (IBM Developer Tools) command. + * Run with the same arguments as `idt`, e.g. + * `node idt.js build` -> `idt build`. + * If `idt` isn't installed, this will prompt you to install. Or you can run + * `node idt.js install` to automatically install idt and any other + * required dependencies (e.g. docker, git, kubernetes, helm). + * + */ + +const fs = require('fs'); +const process = require('process'); +const cp = require('child_process'); +const request = require('request'); +const path = require('path'); + +const chalk = require('chalk'); + +const node = process.execPath; +// Array of args passed to idt.js. +const args = process.argv.slice(2); +let win = (process.platform === 'win32'); + +// Either install idt or run idt + args. +if (args.includes('install')) { + downloadInstaller(); +} else { + // TODO(gib): Check for idt once this works in scripts: + // const checkCmd = win ? 'where idt' : 'which idt'; + const checkCmd = 'bx plugin show dev'; + let hasIDT = false; + try { + console.log(chalk.blue('Checking for idt')); + cp.execSync(checkCmd); // Don't inherit stdio, we don't want to print the output. + hasIDT = true; // If we didn't have idt, the previous command would have thrown. + } catch (e) { + const prompt = require('prompt-confirm'); + new prompt({ name: 'install', + message: 'IDT not found, do you want to install it? y/N', + default: false + }).ask((answer) => { + if (answer) { + downloadInstaller(() => runIDT(args)); + } else { + console.error(chalk.red(`Not installing idt, so not running: idt ${args.join(' ')}`)); + } + }); + } + if (hasIDT) runIDT(args); +} + +// Run IDT with whatever args we were given. +function runIDT(args) { + const cmd = 'bx dev ' + args.join(' '); + console.log(chalk.blue('Running:'), cmd); + cp.execSync(cmd, {stdio: 'inherit'}); +} + +// Download the IDT installer script and trigger runIDT(). +function downloadInstaller(cb) { + const url = win ? + 'https://ibm.biz/yeoman-idt-win-install' : + 'http://ibm.biz/yeoman-idt-install'; + + const fileName = url.split('/').pop() + + console.log(chalk.blue('Downloading installer from:'), url); + + const file = fs.createWriteStream(fileName); + + request + .get({url, followAllRedirects: true}) + .on('error', (err) => { console.error(err); }) + .pipe(file) + .on('finish', () => runInstaller(fileName, cb)); +} + +// Run the installer script and trigger callback (cb). +function runInstaller(fileName, cb) { + const shell = win ? 'powershell.exe' : 'bash'; + + const filePath = path.resolve(__dirname, fileName); + console.log(`Now running: ${shell} ${filePath}`); + + cp.spawnSync(shell, [filePath], {stdio: 'inherit'}); + cb(); +} diff --git a/app/templates/package.json b/app/templates/package.json index aa16e38..796e858 100644 --- a/app/templates/package.json +++ b/app/templates/package.json @@ -10,10 +10,18 @@ "start": "node server/server.js", "start:cluster": "sl-run server/server.js", "debug": "node --debug server/server.js", - "test": "nyc mocha" + "test": "nyc mocha", + "build": "npm run build:idt", + "idt:build": "node idt.js build", + "idt:test": "node idt.js test", + "idt:debug": "node idt.js debug", + "idt:run": "node idt.js run", + "idt:deploy": "node idt.js deploy", + "idt:install": "node idt.js install" }, "dependencies": { "appmetrics-dash": "^3.3.2", + "appmetrics-prometheus": "^0.0.2", "body-parser": "^1.17.2", "express": "^4.15.3", "strong-supervisor": "^6.2.0", @@ -21,8 +29,11 @@ }, "devDependencies": { "chai": "^4.0.0", + "chalk": "^1.1.3", "mocha": "^3.4.2", "nyc": "^10.3.2", - "proxyquire": "^1.8.0" + "prompt-confirm": "^1.2.0", + "proxyquire": "^1.8.0", + "request": "^2.82.0" } } diff --git a/app/templates/server/config/local.json b/app/templates/server/config/local.json index b56246b..8fb2c57 100644 --- a/app/templates/server/config/local.json +++ b/app/templates/server/config/local.json @@ -1,3 +1,3 @@ { - "port": 3000 + "port": 3000 } \ No newline at end of file diff --git a/app/templates/server/routers/public.js b/app/templates/server/routers/public.js index c1c704e..a7f8aab 100644 --- a/app/templates/server/routers/public.js +++ b/app/templates/server/routers/public.js @@ -1,7 +1,7 @@ var express = require('express'); module.exports = function(app){ - var router = express.Router(); - router.use(express.static(process.cwd() + '/public')); - app.use(router); + var router = express.Router(); + router.use(express.static(process.cwd() + '/public')); + app.use(router); } diff --git a/app/templates/server/server.js b/app/templates/server/server.js index f5d49bd..863bb54 100644 --- a/app/templates/server/server.js +++ b/app/templates/server/server.js @@ -1,4 +1,6 @@ require('appmetrics-dash').attach(); +require('appmetrics-prometheus').attach(); + const appName = require('./../package').name; const express = require('express'); const log4js = require('log4js'); @@ -14,12 +16,12 @@ require('./routers/index')(app); const port = process.env.PORT || localConfig.port; app.listen(port, function(){ - logger.info(`<%= bluemix.name %> listening on http://localhost:${port}/appmetrics-dash`); - <% if( !genSwagger ){ %> - logger.info(`<%= bluemix.name %> listening on http://localhost:${port}`); - <% } %> - <% if( genSwagger ){ %> - logger.info(`OpenAPI (Swagger) spec is available at http://localhost:${port}/swagger/api`); - logger.info(`Swagger UI is available at http://localhost:${port}/explorer`); - <% } %> + logger.info(`<%= bluemix.name %> listening on http://localhost:${port}/appmetrics-dash`); + <% if( !genSwagger ){ %> + logger.info(`<%= bluemix.name %> listening on http://localhost:${port}`); + <% } %> + <% if( genSwagger ){ %> + logger.info(`OpenAPI (Swagger) spec is available at http://localhost:${port}/swagger/api`); + logger.info(`Swagger UI is available at http://localhost:${port}/explorer`); + <% } %> }); \ No newline at end of file diff --git a/lib/helpers.js b/lib/helpers.js index d36b632..a1dfa5e 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -22,6 +22,6 @@ exports.reformatPathToNodeExpress = (path) => path.replace(/{/g, ':').replace(/}/g, '') exports.resourceNameFromPath = function (path) { - // grab the first valid element of a path (or partial path) and return it. - return path.match(/^\/*([^/]+)/)[1] + // grab the first valid element of a path (or partial path) and return it. + return path.match(/^\/*([^/]+)/)[1] } diff --git a/package-lock.json b/package-lock.json index 9fa5077..db4b72c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "generator-ibm-core-node-express", - "version": "0.0.60", + "version": "0.0.64", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e418892..1aa9c41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "generator-ibm-core-node-express", - "version": "0.0.60", + "version": "0.0.64", "description": "Yeoman generator for core node express application", "license": "Apache-2.0", "keywords": [ diff --git a/test/common.js b/test/common.js index 7d5fddd..8157d31 100644 --- a/test/common.js +++ b/test/common.js @@ -28,6 +28,7 @@ exports.file = { index_router: 'server/routers/index.js', public: 'server/routers/public.js', index_service: 'server/services/index.js', + idt_js: 'idt.js', cliconfig: 'cli-config.yml' }; @@ -35,4 +36,4 @@ exports.file = { exports.defaultPort = 3000; // The npm start command. -exports.npmStart = "node server.js" \ No newline at end of file +exports.npmStart = "node server.js" diff --git a/test/integration.js b/test/integration.js index 0d90c0b..9b99f33 100644 --- a/test/integration.js +++ b/test/integration.js @@ -69,10 +69,19 @@ describe('core-node-express:app integration test with custom spec', function () "scripts": { "start": "node server/server.js", "start:cluster": "sl-run server/server.js", - "test": "nyc mocha" + "debug": "node --debug server/server.js", + "test": "nyc mocha", + "build": "npm run build:idt", + "idt:build": "node idt.js build", + "idt:test": "node idt.js test", + "idt:debug": "node idt.js debug", + "idt:run": "node idt.js run", + "idt:deploy": "node idt.js deploy", + "idt:install": "node idt.js install" }, "dependencies": { "appmetrics-dash": "^3.3.2", + "appmetrics-prometheus": "^0.0.2", "body-parser": "^1.17.2", "express": "^4.15.3", "strong-supervisor": "^6.2.0", @@ -155,6 +164,7 @@ describe('core-node-express:app integration test with custom bluemix', function }, "dependencies": { "appmetrics-dash": "^3.3.2", + "appmetrics-prometheus": "^0.0.2", "body-parser": "^1.17.2", "express": "^4.15.3", "log4js": "^1.1.1" @@ -185,6 +195,18 @@ describe('core-node-express:app integration test with custom bluemix', function }); }); + describe(common.file.server_js, () => { + it('contains appmetrics-dash attach', () => { + assert.fileContent(common.file.server_js, "require('appmetrics-dash').attach();") + }); + }); + + describe(common.file.server_js, () => { + it('contains appmetrics-prometheus attach', () => { + assert.fileContent(common.file.server_js, "require('appmetrics-prometheus').attach();") + }); + }); + describe(common.file.gitignore, function () { it('contains node_modules', function () { assert.fileContent(common.file.gitignore, 'node_modules'); @@ -237,6 +259,7 @@ describe('core-node-express:app integration test with custom bluemix and spec', }, "dependencies": { "appmetrics-dash": "^3.3.2", + "appmetrics-prometheus": "^0.0.2", "body-parser": "^1.17.2", "express": "^4.15.3", "log4js": "^1.1.1"