diff --git a/package.json b/package.json index f6781ec..087a3c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hotstaq", - "version": "0.8.68", + "version": "0.8.69", "description": "A friendly web framework that fits nicely into devops and CI/CD pipelines.", "bin": { "hotstaq": "./bin/hotstaq" diff --git a/src/Hot.ts b/src/Hot.ts index 553fb55..126259f 100644 --- a/src/Hot.ts +++ b/src/Hot.ts @@ -256,10 +256,24 @@ export class Hot /** * Make an api call. This must include the route version. * + * Simple apiCall: * @example * ```ts * await Hot.apiCall ('/v1/hello_world/echo', { message: "Hello!" }); * ``` + * + * Make an API call and upload a file: + * @example + * ```ts + * let input = document.getElementById ("fileInput"); + * let file = input.files[0]; + * + * await Hot.apiCall ('/v1/hello_world/echo', + * { message: "Hello!" }, HotEventMethod.POST, + * { + * "indexFileKey": file + * }); + * ``` */ static async apiCall (route: string, data: any = null, httpMethod: HotEventMethod = HotEventMethod.POST, diff --git a/src/HotCLI.ts b/src/HotCLI.ts index 3431e5c..4e1baa2 100644 --- a/src/HotCLI.ts +++ b/src/HotCLI.ts @@ -1787,7 +1787,7 @@ export class HotCLI createHotGenerator (); generator.optimizeJS = true; }); - generateCmd.option ("--generate-type ", "The type of output to generate. Can be: javascript,openapi-3.0.0-json,openapi-3.0.0-yaml", + generateCmd.option ("--generate-type ", "The type of output to generate. Can be: javascript,openapi-3.0.0-json,openapi-3.0.0-yaml,asyncapi-2.6.0-json,asyncapi-2.6.0-yaml,", (arg: string, previous: any) => { createHotGenerator (); diff --git a/src/HotGenerator.ts b/src/HotGenerator.ts index 70f4cf7..c4e849e 100644 --- a/src/HotGenerator.ts +++ b/src/HotGenerator.ts @@ -28,6 +28,7 @@ export class HotGenerator * * javascript * * openapi-3.0.0-json * * openapi-3.0.0-yaml + * * asyncapi-2.6.0-json * * asyncapi-2.6.0-yaml */ generateType: string; @@ -55,6 +56,10 @@ export class HotGenerator * The directory to copy all built files to. */ copyTo: string; + /** + * Exit on complete. + */ + exitOnComplete: boolean; constructor (logger: HotLog) { @@ -66,6 +71,7 @@ export class HotGenerator this.logger = logger; this.outputDir = ppath.normalize (`${process.cwd ()}/build-web/`); this.copyTo = ""; + this.exitOnComplete = true; } /** @@ -228,6 +234,7 @@ export class HotGenerator { if ((this.generateType === "openapi-3.0.0-json") || (this.generateType === "openapi-3.0.0-yaml") || + (this.generateType === "asyncapi-2.6.0-json") || (this.generateType === "asyncapi-2.6.0-yaml")) { await this.generateAPIDocumentation (processor, apis); @@ -357,7 +364,9 @@ export class HotGenerator } this.logger.info (`Finished generating Web API "${key}" from HotSite "${hotsite.name}"...`); - process.exit (0); + + if (this.exitOnComplete === true) + process.exit (0); }); } @@ -370,6 +379,7 @@ export class HotGenerator { if (! ((this.generateType === "openapi-3.0.0-json") || (this.generateType === "openapi-3.0.0-yaml") || + (this.generateType === "asyncapi-2.6.0-json") || (this.generateType === "asyncapi-2.6.0-yaml"))) { throw new Error (`Unknown API documentation --generate-type ${JSON.stringify (this.generateType)}`); @@ -384,16 +394,27 @@ export class HotGenerator let jsonObj: any = {}; let components: any = {}; let hotsiteDescription: string = ""; - let servers: any[] = [{ url: serverResult.baseAPIUrl }]; + let servers: any = null; if (hotsite.description != null) hotsiteDescription = hotsite.description; if (this.generateType.indexOf ("openapi-3.0.0") > -1) + { jsonObj.openapi = "3.0.0"; + servers = [{ url: serverResult.baseAPIUrl }]; + } if (this.generateType.indexOf ("asyncapi-2.6.0-yaml") > -1) + { jsonObj.asyncapi = "2.6.0"; + servers = { + server: { + url: serverResult.baseAPIUrl, + protocol: "WebSocket" + } + }; + } let filename: string = `${libraryName}_${apiName}_${this.generateType}`; jsonObj.info = {}; @@ -437,6 +458,17 @@ export class HotGenerator } } + if (jsonObj.asyncapi != null) + { + if (! ((method.type === HotEventMethod.POST_AND_WEBSOCKET_CLIENT_PUB_EVENT) || + (method.type === HotEventMethod.WEBSOCKET_CLIENT_PUB_EVENT))) + { + this.logger.verbose (`Skipping method ${method.name} because it is not a POST_AND_WEBSOCKET_CLIENT_PUB_EVENT or WEBSOCKET_CLIENT_PUB_EVENT method.`); + + continue; + } + } + let methodName: string = method.name; let path: string = `/${route.version}/${routeName}/${methodName}`; let methodDescription: string = ""; @@ -527,13 +559,28 @@ export class HotGenerator } } - jsonObj.paths[path] = {}; - jsonObj.paths[path][method.type.toLowerCase ()] = { - "summary": methodDescription, - responses: { - "200": returnsDescription - } - }; + if (jsonObj.openapi != null) + { + jsonObj.paths[path] = {}; + jsonObj.paths[path][method.type.toLowerCase ()] = { + "summary": methodDescription, + responses: { + "200": returnsDescription + } + }; + } + + if (jsonObj.asyncapi != null) + { + jsonObj.channels[path] = { + publish: { + summary: methodDescription, + message: { + "payload": returnsDescription + } + } + }; + } if (method.parameters != null) { @@ -573,16 +620,27 @@ export class HotGenerator if (component != null) { - jsonObj.paths[path][method.type.toLowerCase ()]["requestBody"] = { - required: true, - content: { - "application/json": { - schema: { - "$ref": `#/components/schemas/${componentName}` + if (jsonObj.openapi != null) + { + jsonObj.paths[path][method.type.toLowerCase ()]["requestBody"] = { + required: true, + content: { + "application/json": { + schema: { + "$ref": `#/components/schemas/${componentName}` + } } } - } - }; + }; + } + + if (jsonObj.asyncapi != null) + { + jsonObj.channels[path].publish = { message: { payload: {} } }; + jsonObj.channels[path].publish.message.payload = { + "$ref": `#/components/schemas/${componentName}` + }; + } } } } @@ -595,10 +653,14 @@ export class HotGenerator let outputFileExtension: string = ".json"; let fileContent: string = ""; - if (this.generateType === "openapi-3.0.0-json") + if ((this.generateType === "openapi-3.0.0-json") || + (this.generateType === "asyncapi-2.6.0-json")) + { fileContent = JSON.stringify (jsonObj, null, 2); + } - if (this.generateType === "openapi-3.0.0-yaml") + if ((this.generateType === "openapi-3.0.0-yaml") || + (this.generateType === "asyncapi-2.6.0-yaml")) { outputFileExtension = ".yaml"; @@ -612,7 +674,9 @@ export class HotGenerator await HotIO.writeTextFile (`${outputFile}${outputFileExtension}`, fileContent); this.logger.info (`Finished generating API Documentation "${key}" from HotSite "${hotsite.name}"...`); - process.exit (0); + + if (this.exitOnComplete === true) + process.exit (0); }); } diff --git a/src/HotIO.ts b/src/HotIO.ts index ba6f2a4..ad5f44b 100644 --- a/src/HotIO.ts +++ b/src/HotIO.ts @@ -35,6 +35,17 @@ export class HotIO })); } + /** + * Write a file stream. + */ + static writeFileStream (path: string, stream: fs.ReadStream): fs.WriteStream + { + let writeStream: fs.WriteStream = fs.createWriteStream (path); + stream.pipe (writeStream); + + return (writeStream); + } + /** * Read a file and create a stream from it. */ diff --git a/src/HotStaq.ts b/src/HotStaq.ts index 9b3dee7..7d8b642 100644 --- a/src/HotStaq.ts +++ b/src/HotStaq.ts @@ -132,7 +132,7 @@ export class HotStaq implements IHotStaq /** * The current version of HotStaq. */ - static version: string = "0.8.68"; + static version: string = "0.8.69"; /** * Indicates if this is a web build. */ diff --git a/tests/create/CreateApp.ts b/tests/create/CreateApp.ts index 8f7180d..c988c53 100644 --- a/tests/create/CreateApp.ts +++ b/tests/create/CreateApp.ts @@ -34,7 +34,7 @@ describe ("Create App Tests", function () creator = new HotCreator (processor.logger, "app"); creator.outputDir = baseDir; - creator.hotstaqVersion = `link`; // Be sure to set the previous version for testing + creator.hotstaqVersion = `0.8.68`; // Be sure to set the previous version for testing await creator.create (); }); it ("should check that the node_modules folder exists", async () => diff --git a/tests/generator/GenerateAPI.ts b/tests/generator/GenerateAPI.ts index 7c30e6d..bedb024 100644 --- a/tests/generator/GenerateAPI.ts +++ b/tests/generator/GenerateAPI.ts @@ -34,6 +34,7 @@ describe ("API Generator Tests", () => { generator = new HotGenerator (processor.logger); generator.hotsites = [processor.hotSite]; + generator.exitOnComplete = false; await generator.generateAPI (processor, apis); }); it ("should check the generated API", async () => @@ -56,4 +57,17 @@ describe ("API Generator Tests", () => expect (hash).to.equal ("0685f7b411e61bc401c805d54f155779331195ecff646f70a1e290988688017f", `The generated API documentation file has changed. Please update the hash in the test.`); }); + it ("should generate the Async API documentation", async () => + { + generator.generateType = "asyncapi-2.6.0-yaml"; + + await generator.generateAPIDocumentation (processor, apis); + }); + it ("should check the generated Async API documentation", async () => + { + const hash: string = await HotIO.sha256File (`./build-web/HotStaqTests_HelloWorldAPI_asyncapi-2.6.0-yaml.yaml`); + + expect (hash).to.equal ("d795de68ff49e48a807121ca059eb91ff7ad5cc00c2b126a2f574b9d6b4727ea", + `The generated API documentation file has changed. Please update the hash in the test.`); + }); }); \ No newline at end of file