diff --git a/dist/DefinitionGenerator.d.ts b/dist/DefinitionGenerator.d.ts new file mode 100644 index 0000000..6f60178 --- /dev/null +++ b/dist/DefinitionGenerator.d.ts @@ -0,0 +1,54 @@ +import { IDefinition, IDefinitionConfig, IServerlessFunctionConfig } from './types'; +export declare class DefinitionGenerator { + version: string; + definition: IDefinition; + config: IDefinitionConfig; + /** + * Constructor + * @param serviceDescriptor IServiceDescription + */ + constructor(config: IDefinitionConfig); + parse(): this; + validate(): { + valid: boolean; + context: string[]; + warnings: any[]; + error?: any[]; + }; + /** + * Add Paths to OpenAPI Configuration from Serverless function documentation + * @param config Add + */ + readFunctions(config: IServerlessFunctionConfig[]): void; + /** + * Cleans schema objects to make them OpenAPI compatible + * @param schema JSON Schema Object + */ + private cleanSchema; + /** + * Generate Operation objects from the Serverless Config. + * + * @link https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/3.0.0.md#operationObject + * @param funcName + * @param documentationConfig + */ + private getOperationFromConfig; + /** + * Derives Path, Query and Request header parameters from Serverless documentation + * @param documentationConfig + */ + private getParametersFromConfig; + /** + * Derives request body schemas from event documentation configuration + * @param documentationConfig + */ + private getRequestBodiesFromConfig; + private attachExamples; + /** + * Gets response bodies from documentation config + * @param documentationConfig + */ + private getResponsesFromConfig; + private getResponseContent; + private getHttpEvents; +} diff --git a/dist/DefinitionGenerator.js b/dist/DefinitionGenerator.js new file mode 100644 index 0000000..8252687 --- /dev/null +++ b/dist/DefinitionGenerator.js @@ -0,0 +1,309 @@ +"use strict"; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const jst_1 = require("@jdw/jst"); +// tslint:disable-next-line no-submodule-imports +const validate_1 = require("swagger2openapi/validate"); +const uuid = require("uuid"); +const utils_1 = require("./utils"); +class DefinitionGenerator { + /** + * Constructor + * @param serviceDescriptor IServiceDescription + */ + constructor(config) { + // The OpenAPI version we currently validate against + this.version = '3.0.0'; + // Base configuration object + this.definition = { + openapi: this.version, + components: {}, + }; + this.config = utils_1.clone(config); + } + parse() { + const { title = '', description = '', version = uuid.v4(), servers = [], models, security = [], } = this.config; + utils_1.merge(this.definition, { + openapi: this.version, + info: { title, description, version }, + servers, + paths: {}, + components: { + schemas: {}, + securitySchemes: security.reduce((result, s) => { + const { authorizerName, name } = s, rest = __rest(s, ["authorizerName", "name"]); + result[name] = rest; + return result; + }, {}), + }, + }); + if (utils_1.isIterable(models)) { + for (const model of models) { + if (!model.schema) { + continue; + } + this.definition.components.schemas[model.name] = this.cleanSchema(jst_1.dereference(model.schema)); + } + } + return this; + } + validate() { + const payload = {}; + try { + validate_1.validateSync(this.definition, payload); + } + catch (error) { + payload.error = error.message; + } + return payload; + } + /** + * Add Paths to OpenAPI Configuration from Serverless function documentation + * @param config Add + */ + readFunctions(config) { + // loop through function configurations + for (const funcConfig of config) { + // loop through http events + for (const httpEvent of this.getHttpEvents(funcConfig.events)) { + const httpEventConfig = httpEvent.http; + if (httpEventConfig.documentation) { + // Build OpenAPI path configuration structure for each method + const pathConfig = { + [`/${httpEventConfig.path}`]: { + [httpEventConfig.method.toLowerCase()]: this.getOperationFromConfig(funcConfig._functionName, httpEventConfig), + }, + }; + // merge path configuration into main configuration + utils_1.merge(this.definition.paths, pathConfig); + } + } + } + } + /** + * Cleans schema objects to make them OpenAPI compatible + * @param schema JSON Schema Object + */ + cleanSchema(schema) { + // Clone the schema for manipulation + const cleanedSchema = utils_1.clone(schema); + // Strip $schema from schemas + if (cleanedSchema.$schema) { + delete cleanedSchema.$schema; + } + // Return the cleaned schema + return cleanedSchema; + } + /** + * Generate Operation objects from the Serverless Config. + * + * @link https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/3.0.0.md#operationObject + * @param funcName + * @param documentationConfig + */ + getOperationFromConfig(funcName, config) { + const documentationConfig = config.documentation; + const operationObj = { + operationId: funcName, + }; + if (documentationConfig.summary) { + operationObj.summary = documentationConfig.summary; + } + if (documentationConfig.description) { + operationObj.description = documentationConfig.description; + } + if (documentationConfig.tags) { + operationObj.tags = documentationConfig.tags; + } + if (documentationConfig.deprecated) { + operationObj.deprecated = true; + } + if (documentationConfig.requestModels) { + operationObj.requestBody = this.getRequestBodiesFromConfig(documentationConfig); + } + operationObj.parameters = this.getParametersFromConfig(documentationConfig); + operationObj.responses = this.getResponsesFromConfig(documentationConfig); + if (config.authorizer && this.config.security) { + const security = this.config.security.find((s) => s.authorizerName === config.authorizer.name); + if (security) { + operationObj.security = [{ [security.name]: [] }]; + } + } + return operationObj; + } + /** + * Derives Path, Query and Request header parameters from Serverless documentation + * @param documentationConfig + */ + getParametersFromConfig(documentationConfig) { + const parameters = []; + // Build up parameters from configuration for each parameter type + for (const type of ['path', 'query', 'header', 'cookie']) { + let paramBlock; + if (type === 'path' && documentationConfig.pathParams) { + paramBlock = documentationConfig.pathParams; + } + else if (type === 'query' && documentationConfig.queryParams) { + paramBlock = documentationConfig.queryParams; + } + else if (type === 'header' && documentationConfig.requestHeaders) { + paramBlock = documentationConfig.requestHeaders; + } + else if (type === 'cookie' && documentationConfig.cookieParams) { + paramBlock = documentationConfig.cookieParams; + } + else { + continue; + } + // Loop through each parameter in a parameter block and add parameters to array + for (const parameter of paramBlock) { + const parameterConfig = { + name: parameter.name, + in: type, + description: parameter.description || '', + required: parameter.required || false, + }; + // if type is path, then required must be true (@see OpenAPI 3.0-RC1) + if (type === 'path') { + parameterConfig.required = true; + } + else if (type === 'query') { + parameterConfig.allowEmptyValue = parameter.allowEmptyValue || false; // OpenAPI default is false + if ('allowReserved' in parameter) { + parameterConfig.allowReserved = parameter.allowReserved || false; + } + } + if ('deprecated' in parameter) { + parameterConfig.deprecated = parameter.deprecated; + } + if ('style' in parameter) { + parameterConfig.style = parameter.style; + parameterConfig.explode = parameter.explode + ? parameter.explode + : parameter.style === 'form'; + } + if (parameter.schema) { + parameterConfig.schema = this.cleanSchema(parameter.schema); + } + if (parameter.example) { + parameterConfig.example = parameter.example; + } + else if (parameter.examples && Array.isArray(parameter.examples)) { + parameterConfig.examples = parameter.examples; + } + if (parameter.content) { + parameterConfig.content = parameter.content; + } + parameters.push(parameterConfig); + } + } + return parameters; + } + /** + * Derives request body schemas from event documentation configuration + * @param documentationConfig + */ + getRequestBodiesFromConfig(documentationConfig) { + const requestBodies = {}; + if (!documentationConfig.requestModels) { + throw new Error(`Required requestModels in: ${JSON.stringify(documentationConfig, null, 2)}`); + } + // Does this event have a request model? + if (documentationConfig.requestModels) { + // For each request model type (Sorted by "Content-Type") + for (const requestModelType of Object.keys(documentationConfig.requestModels)) { + // get schema reference information + const requestModel = this.config.models.filter((model) => model.name === documentationConfig.requestModels[requestModelType]).pop(); + if (requestModel) { + const reqModelConfig = { + schema: { + $ref: `#/components/schemas/${documentationConfig.requestModels[requestModelType]}`, + }, + }; + this.attachExamples(requestModel, reqModelConfig); + const reqBodyConfig = { + content: { + [requestModelType]: reqModelConfig, + }, + }; + if (documentationConfig.requestBody && 'description' in documentationConfig.requestBody) { + reqBodyConfig.description = documentationConfig.requestBody.description; + } + utils_1.merge(requestBodies, reqBodyConfig); + } + } + } + return requestBodies; + } + attachExamples(target, config) { + if (target.examples) { + utils_1.merge(config, { examples: utils_1.clone(target.examples) }); + } + else if (target.example) { + utils_1.merge(config, { example: utils_1.clone(target.example) }); + } + } + /** + * Gets response bodies from documentation config + * @param documentationConfig + */ + getResponsesFromConfig(documentationConfig) { + const responses = {}; + if (documentationConfig.methodResponses) { + for (const response of documentationConfig.methodResponses) { + const methodResponseConfig = { + description: ((response.responseBody && 'description' in response.responseBody) + ? response.responseBody.description + : `Status ${response.statusCode} Response`), + content: this.getResponseContent(response.responseModels), + }; + if (response.responseHeaders) { + methodResponseConfig.headers = {}; + for (const header of response.responseHeaders) { + methodResponseConfig.headers[header.name] = { + description: header.description || `${header.name} header`, + }; + if (header.schema) { + methodResponseConfig.headers[header.name].schema = this.cleanSchema(header.schema); + } + } + } + utils_1.merge(responses, { + [response.statusCode]: methodResponseConfig, + }); + } + } + return responses; + } + getResponseContent(response) { + const content = {}; + for (const responseKey of Object.keys(response)) { + const responseModel = this.config.models.find((model) => model.name === response[responseKey]); + if (responseModel) { + const resModelConfig = { + schema: { + $ref: `#/components/schemas/${response[responseKey]}`, + }, + }; + this.attachExamples(responseModel, resModelConfig); + utils_1.merge(content, { [responseKey]: resModelConfig }); + } + } + return content; + } + getHttpEvents(funcConfig) { + return funcConfig.filter((event) => event.http ? true : false); + } +} +exports.DefinitionGenerator = DefinitionGenerator; +//# sourceMappingURL=DefinitionGenerator.js.map \ No newline at end of file diff --git a/dist/DefinitionGenerator.js.map b/dist/DefinitionGenerator.js.map new file mode 100644 index 0000000..3318dfe --- /dev/null +++ b/dist/DefinitionGenerator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DefinitionGenerator.js","sourceRoot":"","sources":["../src/DefinitionGenerator.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/ServerlessOpenApiDocumentation.d.ts b/dist/ServerlessOpenApiDocumentation.d.ts new file mode 100644 index 0000000..3f62f98 --- /dev/null +++ b/dist/ServerlessOpenApiDocumentation.d.ts @@ -0,0 +1,26 @@ +import { ILog } from './types'; +export declare class ServerlessOpenApiDocumentation { + hooks: any; + commands: any; + /** Serverless Instance */ + private serverless; + /** CLI options */ + /** Serverless Service Custom vars */ + private customVars; + /** + * Constructor + * @param serverless + * @param options + */ + constructor(serverless: any, options: any); + log: ILog; + /** + * Processes CLI input by reading the input from serverless + * @returns config IConfigType + */ + private processCliInput; + /** + * Generates OpenAPI Documentation based on serverless configuration and functions + */ + private generate; +} diff --git a/dist/ServerlessOpenApiDocumentation.js b/dist/ServerlessOpenApiDocumentation.js new file mode 100644 index 0000000..07bcb52 --- /dev/null +++ b/dist/ServerlessOpenApiDocumentation.js @@ -0,0 +1,132 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const c = require("chalk"); +const fs = require("fs"); +const YAML = require("js-yaml"); +const util_1 = require("util"); +const DefinitionGenerator_1 = require("./DefinitionGenerator"); +const utils_1 = require("./utils"); +class ServerlessOpenApiDocumentation { + /** + * Constructor + * @param serverless + * @param options + */ + constructor(serverless, options) { + this.log = (...str) => { + process.stdout.write(str.join(' ')); + }; + // pull the serverless instance into our class vars + this.serverless = serverless; + // pull the CLI options into our class vars + // this.options = options; + // Serverless service custom variables + this.customVars = this.serverless.variables.service.custom; + // Declare the commands this plugin exposes for the Serverless CLI + this.commands = { + openapi: { + commands: { + generate: { + lifecycleEvents: [ + 'serverless', + ], + usage: 'Generate OpenAPI v3 Documentation', + options: { + output: { + usage: 'Output file location [default: openapi.yml|json]', + shortcut: 'o', + }, + format: { + usage: 'OpenAPI file format (yml|json) [default: yml]', + shortcut: 'f', + }, + indent: { + usage: 'File indentation in spaces [default: 2]', + shortcut: 'i', + }, + }, + }, + }, + }, + }; + // Declare the hooks our plugin is interested in + this.hooks = { + 'openapi:generate:serverless': this.generate.bind(this), + }; + } + /** + * Processes CLI input by reading the input from serverless + * @returns config IConfigType + */ + processCliInput() { + const config = { + format: 'yaml', + file: 'openapi.yml', + indent: 2, + }; + config.indent = this.serverless.processedInput.options.indent || 2; + config.format = this.serverless.processedInput.options.format || 'yaml'; + if (['yaml', 'json'].indexOf(config.format.toLowerCase()) < 0) { + throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"'); + } + config.file = this.serverless.processedInput.options.output || + ((config.format === 'yaml') ? 'openapi.yml' : 'openapi.json'); + this.log(`${c.bold.green('[OPTIONS]')}`, `format: "${c.bold.red(config.format)}",`, `output file: "${c.bold.red(config.file)}",`, `indentation: "${c.bold.red(String(config.indent))}"\n\n`); + return config; + } + /** + * Generates OpenAPI Documentation based on serverless configuration and functions + */ + generate() { + this.log(c.bold.underline('OpenAPI v3 Documentation Generator\n\n')); + // Instantiate DocumentGenerator + const generator = new DefinitionGenerator_1.DefinitionGenerator(this.customVars.documentation); + generator.parse(); + // Map function configurations + const funcConfigs = this.serverless.service.getAllFunctions().map((functionName) => { + const func = this.serverless.service.getFunction(functionName); + return utils_1.merge({ _functionName: functionName }, func); + }); + // Add Paths to OpenAPI Output from Function Configuration + generator.readFunctions(funcConfigs); + // Process CLI Input options + const config = this.processCliInput(); + this.log(`${c.bold.yellow('[VALIDATION]')} Validating OpenAPI generated output\n`); + const validation = generator.validate(); + if (validation.valid) { + this.log(`${c.bold.green('[VALIDATION]')} OpenAPI valid: ${c.bold.green('true')}\n\n`); + } + else { + this.log(`${c.bold.red('[VALIDATION]')} Failed to validate OpenAPI document: \n\n`); + this.log(`${c.bold.green('Context:')} ${JSON.stringify(validation.context, null, 2)}\n`); + if (typeof validation.error === 'string') { + this.log(`${validation.error}\n\n`); + } + else { + for (const info of validation.error) { + this.log(c.grey('\n\n--------\n\n')); + this.log(' ', c.blue(info.dataPath), '\n'); + this.log(' ', info.schemaPath, c.bold.yellow(info.message)); + this.log(c.grey('\n\n--------\n\n')); + this.log(`${util_1.inspect(info, { colors: true, depth: 2 })}\n\n`); + } + } + } + const { definition } = generator; + // Output the OpenAPI document to the correct format + let output; + switch (config.format.toLowerCase()) { + case 'json': + output = JSON.stringify(definition, null, config.indent); + break; + case 'yaml': + default: + output = YAML.safeDump(definition, { indent: config.indent }); + break; + } + fs.writeFileSync(config.file, output); + this.log(`${c.bold.green('[OUTPUT]')} To "${c.bold.red(config.file)}"\n`); + } +} +exports.ServerlessOpenApiDocumentation = ServerlessOpenApiDocumentation; +//# sourceMappingURL=ServerlessOpenApiDocumentation.js.map \ No newline at end of file diff --git a/dist/ServerlessOpenApiDocumentation.js.map b/dist/ServerlessOpenApiDocumentation.js.map new file mode 100644 index 0000000..e0811ac --- /dev/null +++ b/dist/ServerlessOpenApiDocumentation.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ServerlessOpenApiDocumentation.js","sourceRoot":"","sources":["../src/ServerlessOpenApiDocumentation.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/__tests__/DefinitionGenerator.spec.d.ts b/dist/__tests__/DefinitionGenerator.spec.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/__tests__/DefinitionGenerator.spec.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/__tests__/DefinitionGenerator.spec.js b/dist/__tests__/DefinitionGenerator.spec.js new file mode 100644 index 0000000..75dba6f --- /dev/null +++ b/dist/__tests__/DefinitionGenerator.spec.js @@ -0,0 +1,57 @@ +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const Serverless = require("serverless"); +const DefinitionGenerator_1 = require("../DefinitionGenerator"); +const utils_1 = require("../utils"); +class ServerlessInterface extends Serverless { + constructor() { + super(...arguments); + this.service = {}; + this.config = {}; + this.yamlParser = {}; + this.pluginManager = {}; + this.variables = {}; + } +} +describe('OpenAPI Documentation Generator', () => { + it('Generates OpenAPI document', () => __awaiter(void 0, void 0, void 0, function* () { + const servicePath = path.join(__dirname, '../../test/project'); + const serverlessYamlPath = path.join(servicePath, './serverless.yml'); + const sls = new Serverless(); + sls.config.update({ + servicePath, + }); + const config = yield sls.yamlParser.parse(serverlessYamlPath); + expect(config).not.toBeNull(); + sls.pluginManager.cliOptions = { stage: 'dev' }; + yield sls.service.load(config); + yield sls.variables.populateService(); + if ('documentation' in sls.service.custom) { + const docGen = new DefinitionGenerator_1.DefinitionGenerator(sls.service.custom.documentation); + docGen.parse(); + // Map function configurations + const funcConfigs = sls.service.getAllFunctions().map((functionName) => { + const func = sls.service.getFunction(functionName); + return utils_1.merge({ _functionName: functionName }, func); + }); + // Add Paths to OpenAPI Output from Function Configuration + docGen.readFunctions(funcConfigs); + expect(docGen.definition).not.toBeNull(); + expect(docGen.definition).toMatchSnapshot(); + } + else { + throw new Error('Cannot find "documentation" in custom section of "serverless.yml"'); + } + })); +}); +//# sourceMappingURL=DefinitionGenerator.spec.js.map \ No newline at end of file diff --git a/dist/__tests__/DefinitionGenerator.spec.js.map b/dist/__tests__/DefinitionGenerator.spec.js.map new file mode 100644 index 0000000..23dfada --- /dev/null +++ b/dist/__tests__/DefinitionGenerator.spec.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DefinitionGenerator.spec.js","sourceRoot":"","sources":["../../src/__tests__/DefinitionGenerator.spec.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,6BAA6B;AAC7B,yCAAyC;AACzC,gEAA6D;AAC7D,oCAAiC;AAEjC,MAAM,mBAAoB,SAAQ,UAAU;IAA5C;;QACS,YAAO,GAAQ,EAAE,CAAC;QAClB,WAAM,GAAQ,EAAE,CAAC;QACjB,eAAU,GAAQ,EAAE,CAAC;QACrB,kBAAa,GAAQ,EAAE,CAAC;QACxB,cAAS,GAAQ,EAAE,CAAC;IAC7B,CAAC;CAAA;AAED,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,4BAA4B,EAAE,GAAS,EAAE;QAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;QAC/D,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC;QACtE,MAAM,GAAG,GAAwB,IAAI,UAAU,EAAE,CAAC;QAElD,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;YAChB,WAAW;SACZ,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAC9B,GAAG,CAAC,aAAa,CAAC,UAAU,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAEhD,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE,CAAC;QAEtC,IAAI,eAAe,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE;YACzC,MAAM,MAAM,GAAG,IAAI,yCAAmB,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YACzE,MAAM,CAAC,KAAK,EAAE,CAAC;YACd,8BAA8B;YAC/B,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE;gBACrE,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;gBACnD,OAAO,aAAK,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,EAAE,IAAI,CAAC,CAAC;YACtD,CAAC,CAAC,CAAC;YAEH,0DAA0D;YAC1D,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;YAElC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,eAAe,EAAE,CAAC;SAC7C;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;SACtF;IACH,CAAC,CAAA,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000..604f82a --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,2 @@ +import { ServerlessOpenApiDocumentation } from './ServerlessOpenApiDocumentation'; +export default ServerlessOpenApiDocumentation; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..c227117 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const ServerlessOpenApiDocumentation_1 = require("./ServerlessOpenApiDocumentation"); +// tslint:disable-next-line no-default-export +exports.default = ServerlessOpenApiDocumentation_1.ServerlessOpenApiDocumentation; +module.exports = ServerlessOpenApiDocumentation_1.ServerlessOpenApiDocumentation; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..e7c029f --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,qFAAkF;AAElF,6CAA6C;AAC7C,kBAAe,+DAA8B,CAAC;AAC9C,MAAM,CAAC,OAAO,GAAG,+DAA8B,CAAC"} \ No newline at end of file diff --git a/dist/types.d.ts b/dist/types.d.ts new file mode 100644 index 0000000..c1eec17 --- /dev/null +++ b/dist/types.d.ts @@ -0,0 +1,78 @@ +export interface IModels { + name: string; + description: string; + contentType: string; + schema: object | any[]; + examples: any[]; + example: object; +} +export interface IDefinitionConfig { + title: string; + description: string; + version?: string; + servers?: IServer[]; + models: IModels[]; + security?: ISecurity[]; +} +export interface ISecurity { + name: string; + authorizerName: string; + type: string; + scheme: string; +} +export interface IDefinitionType { + file: string; + format: 'yaml' | 'json'; + indent: number; +} +export interface IServerlessFunctionConfig { + _functionName: string; + handler: string; + description?: string; + environment?: object; + events?: any[]; +} +export interface IOperation { + tags?: string[]; + summary?: string; + description?: string; + externalDocs?: any; + operationId?: string; + parameters?: IParameterConfig[]; + requestBody?: any; + responses?: any; + callbacks?: any; + deprecated?: boolean; + security?: any[]; + servers?: IServer[]; +} +export interface IServer { + url: string; + descripition: string; +} +export interface IParameterConfig { + name: string; + in: 'path' | 'query' | 'header' | 'cookie'; + description: string; + required?: boolean; + schema?: object; + deprecated?: boolean; + allowEmptyValue?: boolean; + style?: 'form' | 'simple'; + explode?: boolean; + allowReserved?: boolean; + example?: any; + examples?: any[]; + content?: Map; +} +export interface IDefinition { + openapi: string; + info: any; + servers?: any[]; + paths: any; + components?: any; + security?: any[]; + tags?: any[]; + externalDocs: any; +} +export declare type ILog = (...str: string[]) => void; diff --git a/dist/types.js b/dist/types.js new file mode 100644 index 0000000..11e638d --- /dev/null +++ b/dist/types.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/dist/types.js.map b/dist/types.js.map new file mode 100644 index 0000000..c768b79 --- /dev/null +++ b/dist/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/utils.d.ts b/dist/utils.d.ts new file mode 100644 index 0000000..ca6a241 --- /dev/null +++ b/dist/utils.d.ts @@ -0,0 +1,4 @@ +import { IMerge } from 'lutils'; +export declare const merge: IMerge; +export declare const clone: (source: S) => S; +export declare function isIterable(obj: any): boolean; diff --git a/dist/utils.js b/dist/utils.js new file mode 100644 index 0000000..0af3493 --- /dev/null +++ b/dist/utils.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const lutils_1 = require("lutils"); +exports.merge = new lutils_1.Merge({ depth: 100 }).merge; +exports.clone = new lutils_1.Clone({ depth: 100 }).clone; +function isIterable(obj) { + if (obj === null || obj === undefined) { + return false; + } + return typeof obj[Symbol.iterator] === 'function'; +} +exports.isIterable = isIterable; +//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/dist/utils.js.map b/dist/utils.js.map new file mode 100644 index 0000000..e8ed513 --- /dev/null +++ b/dist/utils.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;AAAA,mCAA8C;AAEjC,QAAA,KAAK,GAAW,IAAI,cAAK,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC;AAChD,QAAA,KAAK,GAAG,IAAI,cAAK,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC;AAErD,SAAgB,UAAU,CAAE,GAAG;IAC7B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS,EAAE;QACrC,OAAO,KAAK,CAAC;KACd;IAED,OAAO,OAAO,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,UAAU,CAAC;AACpD,CAAC;AAND,gCAMC"} \ No newline at end of file diff --git a/package.json b/package.json index b9d379d..759cd8b 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "serverless-openapi-documentation", - "version": "0.4.4", + "version": "0.4.5", "description": "Serverless 1.0 plugin to generate OpenAPI V3 documentation from serverless configuration", - "main": "build/index.js", + "main": "dist/index.js", "repository": { "type": "git", "url": "https://github.com/temando/serverless-openapi-documentation.git" @@ -32,7 +32,7 @@ "build:link": "yarn build && cd build && yarn link", "build:watch": "yarn build && tsc --watch", "build": "scripts/build.bash", - "prepare": "yarn build" + "prepare": "npm run build" }, "devDependencies": { "@types/bluebird": "^3.5.8", @@ -55,7 +55,7 @@ "dependencies": { "@jdw/jst": "^2.0.0-beta.15", "bluebird": "^3.5.0", - "chalk": "^2.0.1", + "chalk": "^1.1.3", "fs-extra": "^4.0.1", "js-yaml": "^3.8.4", "lutils": "^2.4.0", diff --git a/src/DefinitionGenerator.ts b/src/DefinitionGenerator.ts index 56732bb..c7f4b63 100644 --- a/src/DefinitionGenerator.ts +++ b/src/DefinitionGenerator.ts @@ -293,7 +293,7 @@ export class DefinitionGenerator { } private attachExamples (target, config) { - if (target.examples && Array.isArray(target.examples)) { + if (target.examples) { merge(config, { examples: clone(target.examples) }); } else if (target.example) { merge(config, { example: clone(target.example) }); diff --git a/tsconfig.json b/tsconfig.json index 8e0db40..14d8f65 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "target": "es6", "module": "commonjs", "moduleResolution": "node", - "outDir": "./build", + "outDir": "./dist", "sourceMap": true, "declaration": true, "noUnusedLocals": true,