Skip to content
This repository has been archived by the owner on Jan 21, 2024. It is now read-only.

Commit

Permalink
Updating CLI to new API
Browse files Browse the repository at this point in the history
  • Loading branch information
jarrodek committed Sep 20, 2018
1 parent 9445539 commit b3e2fa3
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 212 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ node_modules
bower_components
build
output
api-console-cli.log
131 changes: 25 additions & 106 deletions lib/api-base.js
Original file line number Diff line number Diff line change
@@ -1,125 +1,44 @@
'use strict';

const fs = require('fs');
const path = require('path');
const exec = require('child_process').exec;

const winston = require('winston');
/**
* Base class for CLI commands.
*
*/
class ApiBase {
/**
* Sets global options.
*
* @param {Object} opts Command options.
*/
constructor(opts) {
opts = opts || {};
this.verbose = opts.verbose || false;
}
// Prints arguments to the console.
log() {
if (this.verbose) {
console.log.apply(console, arguments);
if (!opts) {
opts = {};
}
this.opts = opts;
this.logger = this.__setupLogger();
}

/**
* Ensures that given path exists in filesystem.
*
* @param {String} path A path to test
* @return {Promise} Resolced promise when the path exists. Rejects when unable to create the
* path.
*/
ensurePath(path) {
return this.pathExists(path)
.catch((exists) => {
if (!exists) {
return this._createDir(path);
}
return Promise.resolve();
});
}
// Creates a directory recursively.
_createDir(dirPath) {
return new Promise((resolve, reject) => {
fs.mkdir(dirPath, (error) => {
if (error && error.code === 'ENOENT') {
this._createDir(path.dirname(dirPath))
.then(() => {
return this._createDir(dirPath);
})
.then(resolve)
.catch(reject);
} else if (error) {
reject(error);
} else {
resolve();
}
});
});
}

/**
* Checks if `path` exists in the filesystem.
*
* @param {String} path A path to check
* @return {Promise} A promise resolves itself if `path` exists and rejects if don't.
* Creates a logger object to log debug output.
* @return {Object}
*/
pathExists(path) {
return new Promise((resolve, reject) => {
fs.access(path, fs.constants.F_OK, (err) => {
if (err) {
return reject();
}
resolve();
});
__setupLogger() {
const level = this.opts.verbose ? 'debug' : 'warn';
this.debugFile = path.join(process.cwd(), 'api-console-cli.log');
const format = winston.format.combine(
winston.format.colorize(),
winston.format.simple()
);
const logger = winston.createLogger({
level,
format,
exitOnError: false,
transports: [
new winston.transports.Console(),
new winston.transports.File({filename: this.debugFile, level: 'error'})
]
});
}

/**
* Execute shell command
*
* @param {String} cmd Command to execute
* @param {String?} dir A directoy where to execute the command.
* @return {Promise} Promise resolves itself if the command was executed successfully and
* rejects it there was an error.
*/
exec(cmd, dir) {
dir = dir || undefined;
return new Promise((resolve, reject) => {
var opts = {};
if (dir) {
opts.cwd = dir;
}
this.log(`Executing command: ${cmd} in dir: ${dir}`);
exec(cmd, opts, (err, stdout, stderr) => {
if (err) {
let currentDir = process.cwd();
if (opts.cwd) {
currentDir = opts.cwd;
}
reject(new Error('Unable to execute command: ' + err.message +
'. Was in dir: ' + currentDir + '. stdout: ', stdout, '. stderr: ', stderr));
return;
}
resolve(stdout);
});
});
}

// Checks if given `url` is a local or remote file.
isRemoteFile(url) {
if (!url) {
// current dir?
return false;
}
if (url.indexOf('http') === 0) {
return true;
}
if (url.indexOf('ftp') === 0) {
return true;
}
return false;
return logger;
}
}
exports.ApiBase = ApiBase;
32 changes: 14 additions & 18 deletions lib/build.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
'use strict';
const ApiBase = require('./api-base').ApiBase;
const consoleBuilder = require('api-console-builder');
const fs = require('fs-extra');
/**
* The ApiBuild class is a command to build the API Console for specific API spec.
* The ApiBuild class is a command to build the API Console
* for specific API spec.
*/
class ApiBuild extends ApiBase {
/**
* Constructs the builder.
* @param {Object} opts Options passed from the command line.
*/
constructor(opts) {
opts = opts || {};
super(opts);
this.opts = opts;
}

/**
* Runs the command.
*
* @return {Promise}
*/
run() {
this.log(' Building the API console. This may take a moment.');
const startTime = Date.now();
this.logger.info(' Building the API console. This may take a moment.');
const moduleOptions = this._prepareOptions(this.opts);
return consoleBuilder(moduleOptions)
.then(() => {
const time = Date.now() - startTime;
this.log(' Console built in ' + time + ' seconds.');
});
// If there's no error here then there's no point of leaving the debug file.
.then(() => fs.remove(this.debugFile));
}

/**
* Creates configuration to be passed to the builder.
* @param {Object} opts User passed options
* @return {Object} Builder options
*/
_prepareOptions(opts) {
const result = {};
// API source options
Expand Down Expand Up @@ -82,6 +77,7 @@ class ApiBuild extends ApiBase {
if (opts.attributes && opts.attributes.length) {
result.attributes = opts.attributes;
}
result.logger = this.logger;
return result;
}
}
Expand Down
118 changes: 90 additions & 28 deletions lib/generate-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const amf = require('amf-client-js');
const fs = require('fs-extra');
const ApiBase = require('./api-base').ApiBase;

const path = require('path');
/**
* Builds a JSON file with the API definition out from the RAML file.
*/
Expand All @@ -14,50 +14,112 @@ class JsonGenerator extends ApiBase {
* @param {Object} opts Options passed from the command line.
*/
constructor(apiFile, opts) {
opts = opts || {};
super(opts);
if (!apiFile) {
throw new Error('The apiFile argument is not specified.');
}
if (!opts.output) {
opts.output = './api-model.json';
if (!this.opts.output) {
this.opts.output = './api-model.json';
}
this.apiFile = apiFile;
this.apiType = opts.apiType;
this.verbose = opts.verbose;
this.output = opts.output;
this.apiType = this.opts.apiType;
this.output = this.opts.output;
}

/**
* Runs the command.
*
* @return {Promise}
*/
run() {
this.log(
'Generating API model from ', this.apiFile, 'using', this.apiType, 'parser');
let msg = 'Generating API model from ' + this.apiFile;
msg += ' using ' + this.apiType + ' parser';
this.logger.info(msg);

amf.plugins.document.WebApi.register();
amf.plugins.document.Vocabularies.register();
amf.plugins.features.AMFValidation.register();
return amf.Core.init()
.then(() => {
const parser = amf.Core.parser(this.apiType, 'application/yaml');
let url;
if (this.apiFile.indexOf('http') === 0) {
url = this.apiFile;
.then(() => this._parse(this.apiFile, this.apiType))
.then((doc) => this._validate(doc, this.apiType))
.then((doc) => this._resolve(doc, this.apiType))
.then((doc) => this._save(doc, this.output));
}
/**
* Parses API file to AMF graph.
* @param {String} location API file location
* @param {String} type API type.
* @return {Promise} Promise resolved to AMF model.
*/
_parse(location, type) {
this.logger.info('AMF ready.');
this.logger.info('Running API parser...');
const parser = amf.Core.parser(type, 'application/yaml');
let url;
if (location.indexOf('http') === 0) {
url = location;
} else {
url = `file://${location}`;
}
return parser.parseFileAsync(url);
}
/**
* Validates API graph
* @param {Object} doc Parsed document
* @param {String} type API type.
* @return {Promise} Promise resolved to the same document.
*/
_validate(doc, type) {
this.logger.info('API parsed.');
this.logger.info('Validating API...');
let validateProfile;
switch (type) {
case 'RAML 1.0': validateProfile = amf.ProfileNames.RAML; break;
case 'RAML 0.8': validateProfile = amf.ProfileNames.RAML08; break;
case 'OAS 2.0':
case 'OAS 3.0':
validateProfile = amf.ProfileNames.OAS;
break;
}
return amf.AMF.validate(doc, validateProfile)
.then((report) => {
if (!report.conforms) {
this.logger.warn(report.toString());
} else {
url = `file://${this.apiFile}`;
this.logger.info('API valid.');
}
return parser.parseFileAsync(url);
})
.then((doc) => {
this.log('API data parsed. Resolving model using "editing" pipeline.');
const resolver = amf.Core.resolver('RAML 1.0');
return resolver.resolve(doc, 'editing');
})
.then((model) => {
this.log('Storing API data model to file.', this.output);
const generator = amf.Core.generator('AMF Graph', 'application/ld+json');
return generator.generateString(model)
.then((data) => fs.writeFile(this.output, data, 'utf8'));
return doc;
});
}
/**
* Validates types in the model
* @param {Object} doc Parsed document
* @param {String} type API type.
* @return {Promise} Promise resolved to the resolved document.
*/
_resolve(doc, type) {
this.logger.info('Resolving API model for API components...');
const resolver = amf.Core.resolver(type);
return resolver.resolve(doc, 'editing');
}
/**
* Generates json-ld model and saves it to specified location.
* @param {Object} doc Document ot use to generate the model
* @param {String} file Output file location
* @return {Promise} Resolved when file is saved.
*/
_save(doc, file) {
this.logger.info('Generating json-ld model...');
const opts = amf.render.RenderOptions().withSourceMaps.withCompactUris;
const generator = amf.Core.generator('AMF Graph', 'application/ld+json');
const start = Date.now();
return generator.generateString(doc, opts)
.then((data) => {
const time = Date.now() - start;
this.logger.info(`Model ready in ${time} milliseconds`);
this.logger.info('Storing API data model to file: ' + file);
const dir = path.dirname(file);
return fs.ensureDir(dir)
.then(() => fs.writeFile(file, data, 'utf8'));
});
}
}
Expand Down
7 changes: 3 additions & 4 deletions lib/serve.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ const url = require('url');
* Based on Polyserve library lightweigth www server to display build results.
*/
class ApiServe extends ApiBase {

/**
* Constructs the builder.
*
Expand All @@ -18,11 +17,11 @@ class ApiServe extends ApiBase {
}

_applyOpts(options) {
var root = options.root;
let root = options.root;
if (!root && options.args && options.args.length) {
root = options.args[0];
}
var opts = {};
const opts = {};
// Set protocol as http by default
opts.protocol = 'http';

Expand All @@ -48,7 +47,7 @@ class ApiServe extends ApiBase {
opts.openPath = options.openPath;
}
if (options.protocol) {
var possibleProtocols = ['http', 'https'];
const possibleProtocols = ['http', 'https'];
opts.protocol = ~possibleProtocols.indexOf(options.protocol) ?
options.protocol : 'http';
}
Expand Down
Loading

0 comments on commit b3e2fa3

Please sign in to comment.