diff --git a/config/all-tags.json b/config/all-tags.json index 9e30fdec6d5c..b2a1c6f3c3ac 100644 --- a/config/all-tags.json +++ b/config/all-tags.json @@ -1 +1 @@ -{"languages":[{"name":"Go/Golang","color":"bg-[#8ECFDF]","borderColor":"border-[#00AFD9]"},{"name":"Java","color":"bg-[#ECA2A4]","borderColor":"border-[#EC2125]"},{"name":"JavaScript","color":"bg-[#F2F1C7]","borderColor":"border-[#BFBE86]"},{"name":"HTML","color":"bg-[#E2A291]","borderColor":"border-[#E44D26]"},{"name":"C/C++","color":"bg-[#93CDEF]","borderColor":"border-[#0080CC]"},{"name":"C#","color":"bg-[#E3AFE0]","borderColor":"border-[#9B4F96]"},{"name":"Python","color":"bg-[#A8D0EF]","borderColor":"border-[#3878AB]"},{"name":"TypeScript","color":"bg-[#7DBCFE]","borderColor":"border-[#2C78C7]"},{"name":"Kotlin","color":"bg-[#B1ACDF]","borderColor":"border-[#756BD9]"},{"name":"Scala","color":"bg-[#FFA299]","borderColor":"border-[#DF301F]"},{"name":"Markdown","color":"bg-[#BABEBF]","borderColor":"border-[#445B64]"},{"name":"YAML","color":"bg-[#FFB764]","borderColor":"border-[#F1901F]"},{"name":"R","color":"bg-[#84B5ED]","borderColor":"border-[#246BBE]"},{"name":"Ruby","color":"bg-[#FF8289]","borderColor":"border-[#FF000F]"},{"name":"Rust","color":"bg-[#FFB8AA]","borderColor":"border-[#E43716]"},{"name":"Shell","color":"bg-[#87D4FF]","borderColor":"border-[#389ED7]"},{"name":"Groovy","color":"bg-[#B6D5E5]","borderColor":"border-[#609DBC]"}],"technologies":[{"name":"Node.js","color":"bg-[#BDFF67]","borderColor":"border-[#84CE24]"},{"name":"Hermes","color":"bg-[#8AEEBD]","borderColor":"border-[#2AB672]"},{"name":"React JS","color":"bg-[#9FECFA]","borderColor":"border-[#08D8FE]"},{"name":".NET","color":"bg-[#A184FF]","borderColor":"border-[#5026D4]"},{"name":"ASP.NET","color":"bg-[#71C2FB]","borderColor":"border-[#1577BC]"},{"name":"Springboot","color":"bg-[#98E279]","borderColor":"border-[#68BC44]"},{"name":"AWS","color":"bg-[#FF9F59]","borderColor":"border-[#EF6703]"},{"name":"Docker","color":"bg-[#B8E0FF]","borderColor":"border-[#2596ED]"},{"name":"Node-RED","color":"bg-[#FF7474]","borderColor":"border-[#8F0101]"},{"name":"Maven","color":"bg-[#FF6B80]","borderColor":"border-[#CA1A33]"},{"name":"Saas","color":"bg-[#6AB8EC]","borderColor":"border-[#2275AD]"},{"name":"Kubernetes-native","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Scala","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Azure","color":"bg-[#4B93FF]","borderColor":"border-[#015ADF]"},{"name":"Jenkins","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Flask","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Nest Js","color":"bg-[#E1224E]","borderColor":"border-[#B9012b]"},{"name":"Socket.IO","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"TypeScript","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Liquid","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Kotlin","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Gradle","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Spring Cloud Streams","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"JHipster JDL","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Groovy","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Markdown","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Shell","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"WebComponents","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Babel","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Storybook","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"AsyncAPI Generator","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"VSCode","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"SmartPaste","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"JetBrains","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"IntelliJ IDEA","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Java","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"HTML","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"}]} \ No newline at end of file +{"languages":[{"name":"Go/Golang","color":"bg-[#8ECFDF]","borderColor":"border-[#00AFD9]"},{"name":"Java","color":"bg-[#ECA2A4]","borderColor":"border-[#EC2125]"},{"name":"JavaScript","color":"bg-[#F2F1C7]","borderColor":"border-[#BFBE86]"},{"name":"HTML","color":"bg-[#E2A291]","borderColor":"border-[#E44D26]"},{"name":"C/C++","color":"bg-[#93CDEF]","borderColor":"border-[#0080CC]"},{"name":"C#","color":"bg-[#E3AFE0]","borderColor":"border-[#9B4F96]"},{"name":"Python","color":"bg-[#A8D0EF]","borderColor":"border-[#3878AB]"},{"name":"TypeScript","color":"bg-[#7DBCFE]","borderColor":"border-[#2C78C7]"},{"name":"Kotlin","color":"bg-[#B1ACDF]","borderColor":"border-[#756BD9]"},{"name":"Scala","color":"bg-[#FFA299]","borderColor":"border-[#DF301F]"},{"name":"Markdown","color":"bg-[#BABEBF]","borderColor":"border-[#445B64]"},{"name":"YAML","color":"bg-[#FFB764]","borderColor":"border-[#F1901F]"},{"name":"R","color":"bg-[#84B5ED]","borderColor":"border-[#246BBE]"},{"name":"Ruby","color":"bg-[#FF8289]","borderColor":"border-[#FF000F]"},{"name":"Rust","color":"bg-[#FFB8AA]","borderColor":"border-[#E43716]"},{"name":"Shell","color":"bg-[#87D4FF]","borderColor":"border-[#389ED7]"},{"name":"Groovy","color":"bg-[#B6D5E5]","borderColor":"border-[#609DBC]"}],"technologies":[{"name":"Node.js","color":"bg-[#BDFF67]","borderColor":"border-[#84CE24]"},{"name":"Hermes","color":"bg-[#8AEEBD]","borderColor":"border-[#2AB672]"},{"name":"React JS","color":"bg-[#9FECFA]","borderColor":"border-[#08D8FE]"},{"name":".NET","color":"bg-[#A184FF]","borderColor":"border-[#5026D4]"},{"name":"ASP.NET","color":"bg-[#71C2FB]","borderColor":"border-[#1577BC]"},{"name":"Springboot","color":"bg-[#98E279]","borderColor":"border-[#68BC44]"},{"name":"AWS","color":"bg-[#FF9F59]","borderColor":"border-[#EF6703]"},{"name":"Docker","color":"bg-[#B8E0FF]","borderColor":"border-[#2596ED]"},{"name":"Node-RED","color":"bg-[#FF7474]","borderColor":"border-[#8F0101]"},{"name":"Maven","color":"bg-[#FF6B80]","borderColor":"border-[#CA1A33]"},{"name":"Saas","color":"bg-[#6AB8EC]","borderColor":"border-[#2275AD]"},{"name":"Kubernetes-native","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Scala","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Azure","color":"bg-[#4B93FF]","borderColor":"border-[#015ADF]"},{"name":"Jenkins","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Flask","color":"bg-[#D7C7F2]","borderColor":"border-[#A387D2]"},{"name":"Nest Js","color":"bg-[#E1224E]","borderColor":"border-[#B9012b]"},{"name":"TypeScript","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Socket.IO","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Liquid","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Kotlin","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Gradle","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Spring Cloud Streams","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"JHipster JDL","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Groovy","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Markdown","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Shell","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"WebComponents","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Babel","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Storybook","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"AsyncAPI Generator","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"VSCode","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"SmartPaste","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"JetBrains","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"IntelliJ IDEA","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"HTML","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"},{"name":"Java","color":"bg-[#61d0f2]","borderColor":"border-[#40ccf7]"}]} \ No newline at end of file diff --git a/config/meetings.json b/config/meetings.json index a6db3de6e5be..2642fcc6ecdf 100644 --- a/config/meetings.json +++ b/config/meetings.json @@ -1,11 +1,4 @@ [ - { - "title": "Essential Building Blocks Working Group", - "calLink": "https://www.google.com/calendar/event?eid=c28zazMxcDk3MThpMWFpNG9lYzRrbmIzNW8gY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/1342", - "banner": "", - "date": "2024-08-27T18:00:00.000Z" - }, { "title": "Marketing WG Meeting", "calLink": "https://www.google.com/calendar/event?eid=MmpwdGxqb29wcHQyaGk3dXU1cTJ0M3E2aGMgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", @@ -13,13 +6,6 @@ "banner": "", "date": "2024-09-10T14:00:00.000Z" }, - { - "title": "Community Meeting", - "calLink": "https://www.google.com/calendar/event?eid=b2lsN3YwcGNmdWZiNTltNm1lM25wYXYwc3MgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", - "url": "https://github.com/asyncapi/community/issues/1355", - "banner": "https://github.com/user-attachments/assets/399c2eec-5123-47e9-ad41-024cd478a667", - "date": "2024-09-03T08:00:00.000Z" - }, { "title": "Community Meeting", "calLink": "https://www.google.com/calendar/event?eid=dG1nMGxqMWg4ajVqZ3Z2NHRpYW42bmUyNWMgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", @@ -138,5 +124,12 @@ "url": "https://github.com/asyncapi/community/issues/1599", "banner": "https://github.com/user-attachments/assets/739381b2-b7de-4e08-b845-04e5a07ba7e0", "date": "2024-12-03T13:00:00.000Z" + }, + { + "title": "Marketing WG Meeting", + "calLink": "https://www.google.com/calendar/event?eid=a3FvM3ZyOTUwcjJpZm5wOWxzZWcwZ3Bja2sgY19xOXRzZWlnbG9tZHNqNm5qdWh2YnB0czExY0Bn", + "url": "https://github.com/asyncapi/community/issues/1616", + "banner": "", + "date": "2024-12-17T14:00:00.000Z" } ] \ No newline at end of file diff --git a/config/newsroom_videos.json b/config/newsroom_videos.json index c9fb91e4a6a9..5d5a467bb308 100644 --- a/config/newsroom_videos.json +++ b/config/newsroom_videos.json @@ -1,16 +1,16 @@ [ - { - "image_url": "https://i.ytimg.com/vi/XirMXiBNaBM/hqdefault.jpg", - "title": "Community Meeting, 16:00 UTC Tuesday November 26th 2024", - "description": "", - "videoId": "XirMXiBNaBM" - }, { "image_url": "https://i.ytimg.com/vi/QWRcCvDmf04/hqdefault.jpg", "title": "AsyncAPI Mentorship Program 2024 Cohort Kick-off", "description": "Welcoming the 2024 cohort of the AsyncAPI mentorship program.", "videoId": "QWRcCvDmf04" }, + { + "image_url": "https://i.ytimg.com/vi/XirMXiBNaBM/hqdefault.jpg", + "title": "Community Meeting, 16:00 UTC Tuesday November 26th 2024", + "description": "", + "videoId": "XirMXiBNaBM" + }, { "image_url": "https://i.ytimg.com/vi/Q2cvxsUUgzA/hqdefault.jpg", "title": "AsyncAPI-Powered Event Feeds: 3 Steps to Streaming", diff --git a/config/tools-automated.json b/config/tools-automated.json index 7a8ac6ee6e6e..0537545cebda 100644 --- a/config/tools-automated.json +++ b/config/tools-automated.json @@ -3,45 +3,45 @@ "description": "The following is a list of APIs that expose functionality related to AsyncAPI.", "toolsList": [ { - "title": "SIO-AsyncAPI", - "description": "This is code-first approach to generate AsyncAPI specification from Socket.IO server.", + "title": "AsyncAPI Server API", + "description": "Server API providing official AsyncAPI tools", "links": { - "websiteUrl": "https://github.com/daler-rahimov/sio-asyncapi", - "docsUrl": "https://github.com/daler-rahimov/sio-asyncapi", - "repoUrl": "https://github.com/daler-rahimov/sio-asyncapi" + "websiteUrl": "https://api.asyncapi.com/v1", + "docsUrl": "https://api.asyncapi.com/v1/docs", + "repoUrl": "https://github.com/asyncapi/server-api" }, "filters": { - "language": "Python", "technology": [ - "Socket.IO", - "Flask" + "Node.js", + "TypeScript" ], "categories": [ - "code-first", "api" ], "hasCommercial": false, - "isAsyncAPIOwner": false + "isAsyncAPIOwner": true } }, { - "title": "AsyncAPI Server API", - "description": "Server API providing official AsyncAPI tools", + "title": "SIO-AsyncAPI", + "description": "This is code-first approach to generate AsyncAPI specification from Socket.IO server.", "links": { - "websiteUrl": "https://api.asyncapi.com/v1", - "docsUrl": "https://api.asyncapi.com/v1/docs", - "repoUrl": "https://github.com/asyncapi/server-api" + "websiteUrl": "https://github.com/daler-rahimov/sio-asyncapi", + "docsUrl": "https://github.com/daler-rahimov/sio-asyncapi", + "repoUrl": "https://github.com/daler-rahimov/sio-asyncapi" }, "filters": { + "language": "Python", "technology": [ - "Node.js", - "TypeScript" + "Socket.IO", + "Flask" ], "categories": [ + "code-first", "api" ], "hasCommercial": false, - "isAsyncAPIOwner": true + "isAsyncAPIOwner": false } } ] @@ -139,6 +139,27 @@ "Code Generators": { "description": "The following is a list of tools that generate code from an AsyncAPI document; not the other way around.", "toolsList": [ + { + "title": "AsyncAPI Modelina", + "description": "Generate payload models into Java, TypeScript, Go, etc, you name it, from AsyncAPI documents. This tool gives you full control over the models through high customization", + "links": { + "websiteUrl": "https://modelina.org", + "docsUrl": "https://github.com/asyncapi/modelina/tree/master/docs", + "repoUrl": "https://github.com/asyncapi/modelina" + }, + "filters": { + "language": "TypeScript", + "technology": [ + "React JS", + "Docker" + ], + "categories": [ + "code-generator" + ], + "hasCommercial": false, + "isAsyncAPIOwner": true + } + }, { "title": "ZenWave SDK", "description": "DDD and API-First for Event-Driven Microservices", @@ -165,27 +186,6 @@ "isAsyncAPIOwner": false } }, - { - "title": "AsyncAPI Modelina", - "description": "Generate payload models into Java, TypeScript, Go, etc, you name it, from AsyncAPI documents. This tool gives you full control over the models through high customization", - "links": { - "websiteUrl": "https://modelina.org", - "docsUrl": "https://github.com/asyncapi/modelina/tree/master/docs", - "repoUrl": "https://github.com/asyncapi/modelina" - }, - "filters": { - "language": "TypeScript", - "technology": [ - "React JS", - "Docker" - ], - "categories": [ - "code-generator" - ], - "hasCommercial": false, - "isAsyncAPIOwner": true - } - }, { "title": "Golang AsyncAPI Code Generator", "description": "Generate Go user and application boilerplate from AsyncAPI specifications. Can be called from `go generate` without requirements.\n", @@ -404,34 +404,34 @@ } }, { - "title": "GitHub Action for Generator", - "description": "CLI to work with your AsyncAPI files. You can validate them and in the future use a generator and even bootstrap a new file. Contributions are welcomed!", + "title": "GitHub Action for CLI", + "description": "GitHub Action with generator, validator, converter and others - all in one for your AsyncAPI documents with AsyncAPI CLI as backbone", "links": { - "repoUrl": "https://github.com/asyncapi/cli" + "repoUrl": "https://github.com/asyncapi/github-action-for-cli" }, "filters": { "technology": [ - "AsyncAPI Generator" + "AsyncAPI CLI" ], "categories": [ - "github-actions" + "github-action" ], "hasCommercial": false, "isAsyncAPIOwner": true } }, { - "title": "GitHub Action for CLI", - "description": "GitHub Action with generator, validator, converter and others - all in one for your AsyncAPI documents with AsyncAPI CLI as backbone", + "title": "GitHub Action for Generator", + "description": "CLI to work with your AsyncAPI files. You can validate them and in the future use a generator and even bootstrap a new file. Contributions are welcomed!", "links": { - "repoUrl": "https://github.com/asyncapi/github-action-for-cli" + "repoUrl": "https://github.com/asyncapi/cli" }, "filters": { "technology": [ - "AsyncAPI CLI" + "AsyncAPI Generator" ], "categories": [ - "github-action" + "github-actions" ], "hasCommercial": false, "isAsyncAPIOwner": true @@ -718,15 +718,18 @@ "description": "The following is a list of templates compatible with AsyncAPI Generator. You can use them to generate apps, clients or documentation from your AsyncAPI documents.", "toolsList": [ { - "title": "Node.js Websockets Template", - "description": "Node.js WebSockets template for the AsyncAPI Generator. It showcases how from a single AsyncAPI document you can generate a server and a client at the same time.", + "title": "Java Spring Cloud Stream Template", + "description": "Java Spring Cloud Stream template for the AsyncAPI Generator", "links": { - "repoUrl": "https://github.com/asyncapi/nodejs-ws-template" + "repoUrl": "https://github.com/asyncapi/java-spring-cloud-stream-template" }, "filters": { - "language": "javascript", + "language": [ + "javascript" + ], "technology": [ - "Node.js" + "Spring Cloud Streams", + "Maven" ], "categories": [ "generator-template" @@ -736,18 +739,19 @@ } }, { - "title": "Java Spring Cloud Stream Template", - "description": "Java Spring Cloud Stream template for the AsyncAPI Generator", + "title": "Java Spring Template", + "description": "Java Spring template for the AsyncAPI Generator", "links": { - "repoUrl": "https://github.com/asyncapi/java-spring-cloud-stream-template" + "repoUrl": "https://github.com/asyncapi/java-spring-template" }, "filters": { "language": [ "javascript" ], "technology": [ - "Spring Cloud Streams", - "Maven" + "Springboot", + "Maven", + "Gradle" ], "categories": [ "generator-template" @@ -757,17 +761,15 @@ } }, { - "title": "Java Template", - "description": "Java template for the AsyncAPI Generator", + "title": "HTML Template", + "description": "HTML template for AsyncAPI Generator. Use it to generate a static docs. It is using AsyncAPI React component under the hood.", "links": { - "repoUrl": "https://github.com/asyncapi/java-template" + "repoUrl": "https://github.com/asyncapi/html-template" }, "filters": { - "language": [ - "javascript" - ], + "language": "javascript", "technology": [ - "Java" + "HTML" ], "categories": [ "generator-template" @@ -777,19 +779,15 @@ } }, { - "title": "Java Spring Template", - "description": "Java Spring template for the AsyncAPI Generator", + "title": "Node.js Multiprotocol Template", + "description": "This template generates a server using your AsyncAPI document. It supports multiple different protocols, like Kafka or MQTT. It is designed in the way that generated code is a library and with it's API you can start the server, send messages or register a middleware for listening incoming messages. Runtime message validation included.", "links": { - "repoUrl": "https://github.com/asyncapi/java-spring-template" + "repoUrl": "https://github.com/asyncapi/nodejs-template" }, "filters": { - "language": [ - "javascript" - ], + "language": "javascript", "technology": [ - "Springboot", - "Maven", - "Gradle" + "Node.js" ], "categories": [ "generator-template" @@ -799,15 +797,15 @@ } }, { - "title": "HTML Template", - "description": "HTML template for AsyncAPI Generator. Use it to generate a static docs. It is using AsyncAPI React component under the hood.", + "title": "Node.js Websockets Template", + "description": "Node.js WebSockets template for the AsyncAPI Generator. It showcases how from a single AsyncAPI document you can generate a server and a client at the same time.", "links": { - "repoUrl": "https://github.com/asyncapi/html-template" + "repoUrl": "https://github.com/asyncapi/nodejs-ws-template" }, "filters": { "language": "javascript", "technology": [ - "HTML" + "Node.js" ], "categories": [ "generator-template" @@ -817,15 +815,17 @@ } }, { - "title": "Node.js Multiprotocol Template", - "description": "This template generates a server using your AsyncAPI document. It supports multiple different protocols, like Kafka or MQTT. It is designed in the way that generated code is a library and with it's API you can start the server, send messages or register a middleware for listening incoming messages. Runtime message validation included.", + "title": "Java Template", + "description": "Java template for the AsyncAPI Generator", "links": { - "repoUrl": "https://github.com/asyncapi/nodejs-template" + "repoUrl": "https://github.com/asyncapi/java-template" }, "filters": { - "language": "javascript", + "language": [ + "javascript" + ], "technology": [ - "Node.js" + "Java" ], "categories": [ "generator-template" diff --git a/dashboard.json b/dashboard.json index a2b37a65dc79..8ecaa6a03cd6 100644 --- a/dashboard.json +++ b/dashboard.json @@ -17,24 +17,15 @@ "score": 34.46095064991105 }, { - "id": "I_kwDODou01c5BZZv-", - "isPR": false, + "id": "PR_kwDOBW5R_c5-T7mG", + "isPR": true, "isAssigned": false, - "title": "Open Graph link preview image according to the document to open", - "author": "smoya", - "resourcePath": "/asyncapi/studio/issues/224", - "repo": "asyncapi/studio", - "labels": [ - { - "name": "enhancement", - "color": "a2eeef" - }, - { - "name": "keep-open", - "color": "f9dd4b" - } - ], - "score": 31.302030173669205 + "title": "feat: add tests for build post list script", + "author": "vishvamsinh28", + "resourcePath": "/asyncapi/website/pull/3284", + "repo": "asyncapi/website", + "labels": [], + "score": 29.866157229922912 }, { "id": "I_kwDOGQYLdM5AX1lK", @@ -54,29 +45,18 @@ "color": "0E8A16" } ], - "score": 28.430284286176615 - }, - { - "id": "PR_kwDOBW5R_c5-T7mG", - "isPR": true, - "isAssigned": false, - "title": "feat: add tests for build post list script", - "author": "vishvamsinh28", - "resourcePath": "/asyncapi/website/pull/3284", - "repo": "asyncapi/website", - "labels": [], - "score": 27.28158593117958 + "score": 28.717458874925875 }, { - "id": "PR_kwDOBW5R_c59FBoR", + "id": "PR_kwDOBW5R_c6BZLuT", "isPR": true, "isAssigned": false, - "title": "feat: add tests for tool-object script", + "title": "feat: add tests for check markdown script", "author": "vishvamsinh28", - "resourcePath": "/asyncapi/website/pull/3265", + "resourcePath": "/asyncapi/website/pull/3378", "repo": "asyncapi/website", "labels": [], - "score": 25.845712987433288 + "score": 23.26114168868996 }, { "id": "PR_kwDOFLhIt85bqKL8", @@ -121,17 +101,6 @@ ], "score": 21.825268744943667 }, - { - "id": "PR_kwDOBW5R_c6BZLuT", - "isPR": true, - "isAssigned": false, - "title": "feat: add tests for check markdown script", - "author": "vishvamsinh28", - "resourcePath": "/asyncapi/website/pull/3378", - "repo": "asyncapi/website", - "labels": [], - "score": 20.67657038994663 - }, { "id": "PR_kwDOFLhIt855u7Eb", "isPR": true, @@ -143,22 +112,6 @@ "labels": [], "score": 20.102221212448114 }, - { - "id": "PR_kwDOBW5R_c535wDj", - "isPR": true, - "isAssigned": false, - "title": "feat: add test for combine tools script", - "author": "vishvamsinh28", - "resourcePath": "/asyncapi/website/pull/3136", - "repo": "asyncapi/website", - "labels": [ - { - "name": "gsoc", - "color": "F4D03F" - } - ], - "score": 17.804824502454043 - }, { "id": "I_kwDOBW5R_c5RVOOY", "isPR": false, @@ -189,9 +142,75 @@ "repo": "asyncapi/community", "labels": [], "score": 16.08177696995849 + }, + { + "id": "I_kwDOFLhIt85bebeO", + "isPR": false, + "isAssigned": false, + "title": "Meeting Banners Storage", + "author": "AceTheCreator", + "resourcePath": "/asyncapi/community/issues/568", + "repo": "asyncapi/community", + "labels": [], + "score": 15.794602381209232 + }, + { + "id": "I_kwDODou01c5BZZv-", + "isPR": false, + "isAssigned": false, + "title": "Open Graph link preview image according to the document to open", + "author": "smoya", + "resourcePath": "/asyncapi/studio/issues/224", + "repo": "asyncapi/studio", + "labels": [ + { + "name": "enhancement", + "color": "a2eeef" + }, + { + "name": "keep-open", + "color": "f9dd4b" + } + ], + "score": 15.087185824232376 + }, + { + "id": "I_kwDODou01c5ZAFWh", + "isPR": false, + "isAssigned": true, + "title": "Please support File References", + "author": "philCryoport", + "resourcePath": "/asyncapi/studio/issues/528", + "repo": "asyncapi/studio", + "labels": [ + { + "name": "enhancement", + "color": "a2eeef" + }, + { + "name": "bounty", + "color": "0E8A16" + } + ], + "score": 14.07155484871368 } ], "goodFirstIssues": [ + { + "id": "I_kwDOFLhIt86ihL5I", + "title": "[BUG] voting summary has some errors in `isVotedInLast3Months`", + "isAssigned": false, + "resourcePath": "/asyncapi/community/issues/1614", + "repo": "asyncapi/community", + "author": "derberg", + "area": "javascript", + "labels": [ + { + "name": "bug", + "color": "d73a4a" + } + ] + }, { "id": "I_kwDOFLhIt86hMsxx", "title": "Create an announcement design for the new TSC member (Ashmit Jagtap)", @@ -608,6 +627,10 @@ { "name": "enhancement", "color": "a2eeef" + }, + { + "name": "stale", + "color": "ededed" } ] }, @@ -664,21 +687,6 @@ } ] }, - { - "id": "I_kwDOBGu-185qGt6A", - "title": "Ensure consistency when using either `Application` or `API` terms", - "isAssigned": false, - "resourcePath": "/asyncapi/spec/issues/949", - "repo": "asyncapi/spec", - "author": "smoya", - "area": "Unknown", - "labels": [ - { - "name": "enhancement", - "color": "a2eeef" - } - ] - }, { "id": "I_kwDOE8Qh385m4AtC", "title": "C# generator add xml docs from the async api description", diff --git a/markdown/docs/tools/cli/usage.md b/markdown/docs/tools/cli/usage.md index 84f59e8803c4..af6eb4531a29 100644 --- a/markdown/docs/tools/cli/usage.md +++ b/markdown/docs/tools/cli/usage.md @@ -27,7 +27,7 @@ $ npm install -g @asyncapi/cli $ asyncapi COMMAND running command... $ asyncapi (--version) -@asyncapi/cli/2.11.0 linux-x64 node-v18.20.5 +@asyncapi/cli/2.12.0 linux-x64 node-v18.20.5 $ asyncapi --help [COMMAND] USAGE $ asyncapi COMMAND @@ -101,7 +101,7 @@ EXAMPLES $ asyncapi bundle ./asyncapi.yaml -o final-asyncapi.yaml --base ../public-api/main.yaml --baseDir ./social-media/comments-service ``` -_See code: [src/commands/bundle.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/bundle.ts)_ +_See code: [src/commands/bundle.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/bundle.ts)_ ## `asyncapi config` @@ -115,7 +115,7 @@ DESCRIPTION CLI config settings ``` -_See code: [src/commands/config/index.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/index.ts)_ +_See code: [src/commands/config/index.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/index.ts)_ ## `asyncapi config analytics` @@ -135,7 +135,7 @@ DESCRIPTION Enable or disable analytics for metrics collection ``` -_See code: [src/commands/config/analytics.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/analytics.ts)_ +_See code: [src/commands/config/analytics.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/analytics.ts)_ ## `asyncapi config context` @@ -149,7 +149,7 @@ DESCRIPTION Manage short aliases for full paths to AsyncAPI documents ``` -_See code: [src/commands/config/context/index.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/index.ts)_ +_See code: [src/commands/config/context/index.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/index.ts)_ ## `asyncapi config context add CONTEXT-NAME SPEC-FILE-PATH` @@ -171,7 +171,7 @@ DESCRIPTION Add a context to the store ``` -_See code: [src/commands/config/context/add.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/add.ts)_ +_See code: [src/commands/config/context/add.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/add.ts)_ ## `asyncapi config context current` @@ -188,7 +188,7 @@ DESCRIPTION Shows the current context that is being used ``` -_See code: [src/commands/config/context/current.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/current.ts)_ +_See code: [src/commands/config/context/current.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/current.ts)_ ## `asyncapi config context edit CONTEXT-NAME NEW-SPEC-FILE-PATH` @@ -209,7 +209,7 @@ DESCRIPTION Edit a context in the store ``` -_See code: [src/commands/config/context/edit.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/edit.ts)_ +_See code: [src/commands/config/context/edit.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/edit.ts)_ ## `asyncapi config context init [CONTEXT-FILE-PATH]` @@ -232,7 +232,7 @@ DESCRIPTION Initialize context ``` -_See code: [src/commands/config/context/init.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/init.ts)_ +_See code: [src/commands/config/context/init.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/init.ts)_ ## `asyncapi config context list` @@ -249,7 +249,7 @@ DESCRIPTION List all the stored contexts in the store ``` -_See code: [src/commands/config/context/list.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/list.ts)_ +_See code: [src/commands/config/context/list.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/list.ts)_ ## `asyncapi config context remove CONTEXT-NAME` @@ -269,7 +269,7 @@ DESCRIPTION Delete a context from the store ``` -_See code: [src/commands/config/context/remove.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/remove.ts)_ +_See code: [src/commands/config/context/remove.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/remove.ts)_ ## `asyncapi config context use CONTEXT-NAME` @@ -289,7 +289,7 @@ DESCRIPTION Set a context as current ``` -_See code: [src/commands/config/context/use.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/context/use.ts)_ +_See code: [src/commands/config/context/use.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/context/use.ts)_ ## `asyncapi config versions` @@ -306,7 +306,7 @@ DESCRIPTION Show versions of AsyncAPI tools used ``` -_See code: [src/commands/config/versions.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/config/versions.ts)_ +_See code: [src/commands/config/versions.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/config/versions.ts)_ ## `asyncapi convert [SPEC-FILE]` @@ -334,7 +334,7 @@ DESCRIPTION Convert asyncapi documents older to newer versions or OpenAPI/postman-collection documents to AsyncAPI ``` -_See code: [src/commands/convert.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/convert.ts)_ +_See code: [src/commands/convert.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/convert.ts)_ ## `asyncapi diff OLD NEW` @@ -375,7 +375,7 @@ DESCRIPTION Find diff between two asyncapi files ``` -_See code: [src/commands/diff.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/diff.ts)_ +_See code: [src/commands/diff.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/diff.ts)_ ## `asyncapi format [SPEC-FILE]` @@ -398,7 +398,7 @@ DESCRIPTION Convert asyncapi documents from any format to yaml, yml or JSON ``` -_See code: [src/commands/format.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/format.ts)_ +_See code: [src/commands/format.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/format.ts)_ ## `asyncapi generate` @@ -412,7 +412,7 @@ DESCRIPTION Generate typed models or other things like clients, applications or docs using AsyncAPI Generator templates. ``` -_See code: [src/commands/generate/index.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/generate/index.ts)_ +_See code: [src/commands/generate/index.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/generate/index.ts)_ ## `asyncapi generate fromTemplate ASYNCAPI TEMPLATE` @@ -457,7 +457,7 @@ EXAMPLES $ asyncapi generate fromTemplate asyncapi.yaml @asyncapi/html-template --param version=1.0.0 singleFile=true --output ./docs --force-write ``` -_See code: [src/commands/generate/fromTemplate.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/generate/fromTemplate.ts)_ +_See code: [src/commands/generate/fromTemplate.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/generate/fromTemplate.ts)_ ## `asyncapi generate models LANGUAGE FILE` @@ -527,7 +527,7 @@ DESCRIPTION Generates typed models ``` -_See code: [src/commands/generate/models.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/generate/models.ts)_ +_See code: [src/commands/generate/models.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/generate/models.ts)_ ## `asyncapi new` @@ -585,7 +585,7 @@ EXAMPLES $ asyncapi new --file-name=my-asyncapi.yml --example=default-example.yml --no-tty - create a new file with a specific name, using one of the examples and without interactive mode ``` -_See code: [src/commands/new/index.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/new/index.ts)_ +_See code: [src/commands/new/index.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/new/index.ts)_ ## `asyncapi new file` @@ -643,7 +643,7 @@ EXAMPLES $ asyncapi new --file-name=my-asyncapi.yml --example=default-example.yml --no-tty - create a new file with a specific name, using one of the examples and without interactive mode ``` -_See code: [src/commands/new/file.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/new/file.ts)_ +_See code: [src/commands/new/file.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/new/file.ts)_ ## `asyncapi new glee` @@ -665,7 +665,7 @@ DESCRIPTION Creates a new Glee project ``` -_See code: [src/commands/new/glee.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/new/glee.ts)_ +_See code: [src/commands/new/glee.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/new/glee.ts)_ ## `asyncapi new template` @@ -689,7 +689,7 @@ DESCRIPTION Creates a new template ``` -_See code: [src/commands/new/template.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/new/template.ts)_ +_See code: [src/commands/new/template.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/new/template.ts)_ ## `asyncapi optimize [SPEC-FILE]` @@ -731,7 +731,7 @@ EXAMPLES $ asyncapi optimize ./asyncapi.yaml --ignore=schema ``` -_See code: [src/commands/optimize.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/optimize.ts)_ +_See code: [src/commands/optimize.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/optimize.ts)_ ## `asyncapi pretty SPEC-FILE` @@ -756,7 +756,7 @@ EXAMPLES $ asyncapi pretty ./asyncapi.yaml --output formatted-asyncapi.yaml ``` -_See code: [src/commands/pretty.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/pretty.ts)_ +_See code: [src/commands/pretty.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/pretty.ts)_ ## `asyncapi start` @@ -770,7 +770,7 @@ DESCRIPTION Starts AsyncAPI-related services. Currently, it supports launching the AsyncAPI Studio ``` -_See code: [src/commands/start/index.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/start/index.ts)_ +_See code: [src/commands/start/index.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/start/index.ts)_ ## `asyncapi start studio` @@ -789,7 +789,7 @@ DESCRIPTION starts a new local instance of Studio ``` -_See code: [src/commands/start/studio.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/start/studio.ts)_ +_See code: [src/commands/start/studio.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/start/studio.ts)_ ## `asyncapi validate [SPEC-FILE]` @@ -820,5 +820,5 @@ DESCRIPTION validate asyncapi file ``` -_See code: [src/commands/validate.ts](https://github.com/asyncapi/cli/blob/v2.11.0/src/commands/validate.ts)_ +_See code: [src/commands/validate.ts](https://github.com/asyncapi/cli/blob/v2.12.0/src/commands/validate.ts)_ diff --git a/package-lock.json b/package-lock.json index 87b500419b87..82080d33a35c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13722,9 +13722,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -13745,7 +13745,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -13760,6 +13760,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -23339,9 +23343,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/path-type": { "version": "4.0.0", diff --git a/scripts/tools/combine-tools.js b/scripts/tools/combine-tools.js index 602262428fa1..1b1367b15ccb 100644 --- a/scripts/tools/combine-tools.js +++ b/scripts/tools/combine-tools.js @@ -106,37 +106,44 @@ const getFinalTool = async (toolObject) => { // Combine the automated tools and manual tools list into single JSON object file, and // lists down all the language and technology tags in one JSON file. const combineTools = async (automatedTools, manualTools, toolsPath, tagsPath) => { - for (const key in automatedTools) { - let finalToolsList = []; - if (automatedTools[key].toolsList.length) { - for (const tool of automatedTools[key].toolsList) { - finalToolsList.push(await getFinalTool(tool)) + try { + for (const key in automatedTools) { + let finalToolsList = []; + if (automatedTools[key].toolsList.length) { + for (const tool of automatedTools[key].toolsList) { + finalToolsList.push(await getFinalTool(tool)) + } } - } - if (manualTools[key] && manualTools[key].toolsList.length) { - for (const tool of manualTools[key].toolsList) { - let isAsyncAPIrepo; - const isValid = await validate(tool) - if (isValid) { - if (tool?.links?.repoUrl) { - const url = new URL(tool.links.repoUrl) - isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") - } else isAsyncAPIrepo = false - let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) - finalToolsList.push(await getFinalTool(toolObject)) - } else { - console.error('Script is not failing, it is just dropping errors for further investigation'); - console.error(`Invalid ${tool.title} .asyncapi-tool file.`); - console.error(`Located in manual-tools.json file`); - console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); + if (manualTools[key]?.toolsList?.length) { + for (const tool of manualTools[key].toolsList) { + let isAsyncAPIrepo; + const isValid = await validate(tool) + if (isValid) { + if (tool?.links?.repoUrl) { + const url = new URL(tool.links.repoUrl) + isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") + } else isAsyncAPIrepo = false + let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) + finalToolsList.push(await getFinalTool(toolObject)) + } else { + console.error({ + message: 'Tool validation failed', + tool: tool.title, + source: 'manual-tools.json', + errors: validate.errors, + note: 'Script continues execution, error logged for investigation' + }); + } } } + finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); + finalTools[key].toolsList = finalToolsList } - finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); - finalTools[key].toolsList = finalToolsList + fs.writeFileSync(toolsPath, JSON.stringify(finalTools)); + fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),) + } catch (err) { + throw new Error(`Error combining tools: ${err}`); } - fs.writeFileSync(toolsPath,JSON.stringify(finalTools)); - fs.writeFileSync(tagsPath,JSON.stringify({ languages: languageList, technologies: technologyList }),) } -module.exports = { combineTools } \ No newline at end of file +module.exports = { combineTools } diff --git a/scripts/tools/tools-object.js b/scripts/tools/tools-object.js index e5a30334f7d3..1d8c73f8074b 100644 --- a/scripts/tools/tools-object.js +++ b/scripts/tools/tools-object.js @@ -9,7 +9,6 @@ addFormats(ajv, ["uri"]) const validate = ajv.compile(schema) const { convertToJson } = require('../utils'); - // Config options set for the Fuse object const options = { includeScore: true, @@ -25,8 +24,8 @@ const fuse = new Fuse(categoryList, options) // isAsyncAPIrepo boolean variable to define whether the tool repository is under // AsyncAPI organization or not, to create a JSON tool object as required in the frontend // side to show ToolCard. -const createToolObject = async (toolFile, repositoryUrl='', repoDescription='', isAsyncAPIrepo='') => { - let resultantObject = { +const createToolObject = async (toolFile, repositoryUrl = '', repoDescription = '', isAsyncAPIrepo = '') => { + const resultantObject = { title: toolFile.title, description: toolFile?.description ? toolFile.description : repoDescription, links: { @@ -47,67 +46,71 @@ const createToolObject = async (toolFile, repositoryUrl='', repoDescription='', // and creating a JSON tool object in which all the tools are listed in defined // categories order, which is then updated in `automated-tools.json` file. async function convertTools(data) { - let finalToolsObject = {}; - const dataArray = data.items; - - // initialising finalToolsObject with all categories inside it with proper elements in each category - for (var index in categoryList) { - finalToolsObject[categoryList[index].name] = { - description: categoryList[index].description, - toolsList: [] - }; - } + try { + let finalToolsObject = {}; + const dataArray = data.items; - for (let tool of dataArray) { - try { - if (tool.name.startsWith('.asyncapi-tool')) { - // extracting the reference id of the repository which will be used to extract the path of the .asyncapi-tool file in the Tools repository - // ex: for a url = "https://api.github.com/repositories/351453552/contents/.asyncapi-tool?ref=61855e7365a881e98c2fe667a658a0005753d873" - // the text (id) present after '=' gives us a reference id for the repo - let reference_id = tool.url.split("=")[1]; - let download_url = `https://raw.githubusercontent.com/${tool.repository.full_name}/${reference_id}/${tool.path}`; + // initialising finalToolsObject with all categories inside it with proper elements in each category + finalToolsObject = Object.fromEntries( + categoryList.map((category) => [ + category.name, + { + description: category.description, + toolsList: [] + } + ]) + ); - const { data: toolFileContent } = await axios.get(download_url); + await Promise.all(dataArray.map(async (tool) => { + try { + if (tool.name.startsWith('.asyncapi-tool')) { + const referenceId = tool.url.split('=')[1]; + const downloadUrl = `https://raw.githubusercontent.com/${tool.repository.full_name}/${referenceId}/${tool.path}`; - //some stuff can be YAML - const jsonToolFileContent = await convertToJson(toolFileContent) + const { data: toolFileContent } = await axios.get(downloadUrl); - //validating against JSON Schema for tools file - const isValid = await validate(jsonToolFileContent) + //some stuff can be YAML + const jsonToolFileContent = await convertToJson(toolFileContent) - if (isValid) { - let repositoryUrl = tool.repository.html_url; - let repoDescription = tool.repository.description; - let isAsyncAPIrepo = tool.repository.owner.login === "asyncapi"; - let toolObject = await createToolObject(jsonToolFileContent, repositoryUrl, repoDescription, isAsyncAPIrepo); + //validating against JSON Schema for tools file + const isValid = await validate(jsonToolFileContent) - // Tool Object is appended to each category array according to Fuse search for categories inside Tool Object - jsonToolFileContent.filters.categories.forEach(async (category) => { - const categorySearch = await fuse.search(category); + if (isValid) { + const repositoryUrl = tool.repository.html_url; + const repoDescription = tool.repository.description; + const isAsyncAPIrepo = tool.repository.owner.login === 'asyncapi'; + const toolObject = await createToolObject( + jsonToolFileContent, + repositoryUrl, + repoDescription, + isAsyncAPIrepo + ); - if (categorySearch.length) { - let searchedCategoryName = categorySearch[0].item.name - if (!finalToolsObject[searchedCategoryName].toolsList.find((element => element === toolObject))) - finalToolsObject[searchedCategoryName].toolsList.push(toolObject); - } else { - // if Tool object has a category, not defined in our categorylist, then this provides a `other` category to the tool. - if (!finalToolsObject['Others'].toolsList.find((element => element === toolObject))) - finalToolsObject['Others'].toolsList.push(toolObject); - } - }); - } else { - console.error('Script is not failing, it is just dropping errors for further investigation'); - console.error('Invalid .asyncapi-tool file.'); - console.error(`Located in: ${tool.html_url}`); - console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); + // Tool Object is appended to each category array according to Fuse search for categories inside Tool Object + await Promise.all(jsonToolFileContent.filters.categories.map(async (category) => { + const categorySearch = await fuse.search(category); + const targetCategory = categorySearch.length ? categorySearch[0].item.name : 'Others'; + const { toolsList } = finalToolsObject[targetCategory]; + if (!toolsList.includes(toolObject)) { + toolsList.push(toolObject); + } + })); + } else { + console.error('Script is not failing, it is just dropping errors for further investigation'); + console.error('Invalid .asyncapi-tool file.'); + console.error(`Located in: ${tool.html_url}`); + console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); + } } + } catch (err) { + console.error(err) + throw err; } - } catch (err) { - console.error(err) - throw err; - } + })) + return finalToolsObject; + } catch (err) { + throw new Error(`Error processing tool: ${err.message}`) } - return finalToolsObject; } -module.exports = {convertTools, createToolObject} \ No newline at end of file +module.exports = { convertTools, createToolObject } diff --git a/tests/fixtures/combineToolsData.js b/tests/fixtures/combineToolsData.js new file mode 100644 index 000000000000..452764ac9192 --- /dev/null +++ b/tests/fixtures/combineToolsData.js @@ -0,0 +1,212 @@ +const expectedDataT1 = { + languages: [ + { + name: 'JavaScript', + color: 'bg-[#57f281]', + borderColor: 'border-[#37f069]', + }, + { + name: 'Python', + color: 'bg-[#3572A5]', + borderColor: 'border-[#3572A5]', + }, + ], + technologies: [ + { + name: 'Node.js', + color: 'bg-[#61d0f2]', + borderColor: 'border-[#40ccf7]', + }, + { + name: 'Flask', + color: 'bg-[#000000]', + borderColor: 'border-[#FFFFFF]', + }, + ], +}; + +const manualToolsWithMissingData = [ + { + title: 'Tool C', + filters: {}, + links: { repoUrl: 'https://github.com/asyncapi/tool-c' }, + }, +]; + +const manualToolsToSort = { + category1: { + description: 'Sample Category', + toolsList: [ + { + title: 'Tool Z', + filters: { language: 'JavaScript' }, + links: { repoUrl: 'https://github.com/asyncapi/tool-z' }, + }, + { + title: 'Tool A', + filters: { language: 'Python' }, + links: { repoUrl: 'https://github.com/asyncapi/tool-a' }, + }, + ], + }, +}; + +const toolWithMultipleLanguages = { + title: 'Multi-Language Tool', + filters: { + language: ['JavaScript', 'Python', 'NewLanguage'], + technology: ['Node.js'], + }, + links: { repoUrl: 'https://github.com/example/multi-language-tool' }, +}; + +const automatedToolsT5 = { + category1: { + description: 'Category 1 Description', + toolsList: [toolWithMultipleLanguages], + }, +}; + +const invalidToolT4 = { title: 'Invalid Tool' }; + +const automatedToolsT4 = { + category1: { + description: 'Category 1 Description', + toolsList: [], + }, +}; +const manualToolsT4 = { + category1: { + toolsList: [invalidToolT4], + }, +}; + +const toolWithNewTagsT6 = { + title: 'New Tags Tool', + filters: { + language: 'NewLanguage', + technology: ['NewTechnology'], + }, + links: { repoUrl: 'https://github.com/example/new-tags-tool' }, +}; + +const automatedToolsT6 = { + category1: { + description: 'Category 1 Description', + toolsList: [toolWithNewTagsT6], + }, +}; + +const toolWithNewLanguageT7 = { + title: 'New Language Tool', + filters: { + language: 'Go', + technology: ['Node.js'], + }, + links: { repoUrl: 'https://github.com/example/new-language-tool' }, +}; + +const automatedToolsT7 = { + category1: { + description: 'Category 1 Description', + toolsList: [toolWithNewLanguageT7], + }, +}; + +const validToolT8 = { + title: 'Valid Tool', + filters: { + language: 'JavaScript', + technology: ['Node.js'], + }, + links: { repoUrl: 'https://github.com/asyncapi/valid-tool' }, +}; + +const automatedToolsT8 = { + category1: { + description: 'Category 1 Description', + toolsList: [], + }, +}; + +const manualToolsT8 = { + category1: { + toolsList: [validToolT8], + }, +}; + +const toolWithoutRepoUrlT9 = { + title: 'Tool Without Repo', + filters: { + language: 'Python', + technology: ['Flask'], + }, + links: {}, +}; + +const automatedToolsT9 = { + category1: { + description: 'Category 1 Description', + toolsList: [], + }, +}; + +const manualToolsT9 = { + category1: { + toolsList: [toolWithoutRepoUrlT9], + }, +}; + +const invalidAutomatedToolsT10 = { + invalidCategory: { + description: 'Invalid Category Description', + toolsList: [], + }, +}; + +const manualToolsWithInvalidURLT11 = { + category1: { + toolsList: [ + { + title: 'Tool with Invalid URL', + filters: { language: 'JavaScript' }, + links: { repoUrl: 'invalid-url' }, + }, + ], + }, +}; + +const circularTool = { + title: 'Circular Tool', + filters: { + language: 'JavaScript', + technology: ['Node.js'], + }, + links: { repoUrl: 'https://github.com/asyncapi/circular-tool' }, +}; + +const automatedToolsT12 = { + category1: { + description: 'Category 1', + toolsList: [circularTool], + }, +}; + +module.exports = { + expectedDataT1, + manualToolsWithMissingData, + manualToolsToSort, + automatedToolsT5, + automatedToolsT4, + manualToolsT4, + automatedToolsT6, + automatedToolsT7, + automatedToolsT8, + manualToolsT8, + automatedToolsT9, + manualToolsT9, + circularTool, + automatedToolsT12, + invalidAutomatedToolsT10, + manualToolsWithInvalidURLT11, +}; diff --git a/tests/fixtures/tools/automated-tools.json b/tests/fixtures/tools/automated-tools.json new file mode 100644 index 000000000000..1184da03e718 --- /dev/null +++ b/tests/fixtures/tools/automated-tools.json @@ -0,0 +1,17 @@ +{ + "category1": { + "description": "Sample Category", + "toolsList": [ + { + "title": "Tool B", + "filters": { + "language": "Python", + "technology": ["Flask"] + }, + "links": { + "repoUrl": "https://github.com/asyncapi/tool-b" + } + } + ] + } +} diff --git a/tests/fixtures/tools/manual-tools.json b/tests/fixtures/tools/manual-tools.json new file mode 100644 index 000000000000..47469bc1a7e7 --- /dev/null +++ b/tests/fixtures/tools/manual-tools.json @@ -0,0 +1,12 @@ +[ + { + "title": "Tool A", + "filters": { + "language": "JavaScript", + "technology": ["Node.js"] + }, + "links": { + "repoUrl": "https://github.com/asyncapi/tool-a" + } + } +] diff --git a/tests/helper/toolsObjectData.js b/tests/helper/toolsObjectData.js new file mode 100644 index 000000000000..929d985f9773 --- /dev/null +++ b/tests/helper/toolsObjectData.js @@ -0,0 +1,79 @@ +const createToolRepositoryData = ({ + name = '.asyncapi-tool', + refId = '61855e7365a881e98c2fe667a658a0005753d873', + owner = 'asyncapi', + repoName = 'example-repo', + description = 'Example repository', + path = '.asyncapi-tool' +} = {}) => ({ + name, + url: `https://api.github.com/repositories/351453552/contents/${path}?ref=${refId}`, + repository: { + full_name: `${owner}/${repoName}`, + html_url: `https://github.com/${owner}/${repoName}`, + description, + owner: { login: owner } + }, + path +}); + +const createToolFileContent = ({ + title = 'Example Tool', + description = 'This is an example tool.', + repoUrl = null, + categories = ['Category1'], + hasCommercial = false, + additionalLinks = {}, + additionalFilters = {} +} = {}) => ({ + title, + description, + links: { + repoUrl: repoUrl || `https://github.com/asyncapi/${encodeURIComponent(title.toLowerCase().replace(/\s+/g, '-'))}`, + ...additionalLinks + }, + filters: { categories, hasCommercial, ...additionalFilters } +}); + +const createExpectedToolObject = ({ + title = 'Example Tool', + description = 'This is an example tool.', + repoUrl = null, + categories = ['Category1'], + hasCommercial = false, + isAsyncAPIOwner = true, + additionalLinks = {}, + additionalFilters = {} +} = {}) => + createToolFileContent({ + title, + description, + repoUrl, + categories, + hasCommercial, + additionalLinks, + additionalFilters: { isAsyncAPIOwner, ...additionalFilters } + }); + +const createMockData = (tools = []) => ({ + items: tools.map((tool) => + typeof tool === 'string' + ? createToolRepositoryData({ name: `.asyncapi-tool-${tool}`, repoName: tool }) + : createToolRepositoryData(tool) + ) +}); + +const createMalformedYAML = ({ + title = 'Malformed Tool', + description = 'This tool has malformed YAML.', + repoUrl = 'https://github.com/asyncapi/malformed-repo' } = {}) => ` + title: ${title} + description: ${description} + links: + repoUrl: ${repoUrl} + filters: + categories: + - Category1 +`; + +module.exports = { createToolFileContent, createExpectedToolObject, createMockData, createMalformedYAML }; diff --git a/tests/tools/combine-tools.test.js b/tests/tools/combine-tools.test.js new file mode 100644 index 000000000000..622067a57462 --- /dev/null +++ b/tests/tools/combine-tools.test.js @@ -0,0 +1,229 @@ +const fs = require('fs'); +const path = require('path'); +const { combineTools } = require('../../scripts/tools/combine-tools'); +const { + expectedDataT1, + manualToolsWithMissingData, + manualToolsToSort, + automatedToolsT5, + automatedToolsT4, + manualToolsT4, + automatedToolsT6, + automatedToolsT7, + automatedToolsT8, + manualToolsT8, + automatedToolsT9, + manualToolsT9, + automatedToolsT12, + invalidAutomatedToolsT10, + manualToolsWithInvalidURLT11, + circularTool +} = require('../fixtures/combineToolsData'); + +jest.mock('ajv', () => { + return jest.fn().mockImplementation(() => ({ + compile: jest.fn().mockImplementation(() => (data) => data.title !== 'Invalid Tool'), + })); +}); + + +jest.mock('ajv-formats', () => { + return jest.fn(); +}); + +jest.mock('../../scripts/tools/tags-color', () => ({ + languagesColor: [ + { name: 'JavaScript', color: 'bg-[#57f281]', borderColor: 'border-[#37f069]' }, + { name: 'Python', color: 'bg-[#3572A5]', borderColor: 'border-[#3572A5]' } + ], + technologiesColor: [ + { name: 'Node.js', color: 'bg-[#61d0f2]', borderColor: 'border-[#40ccf7]' }, + { name: 'Flask', color: 'bg-[#000000]', borderColor: 'border-[#FFFFFF]' } + ] +})); + +jest.mock('../../scripts/tools/categorylist', () => ({ + categoryList: [ + { name: 'category1', description: 'Sample Category 1' }, + { name: 'category2', description: 'Sample Category 2' } + ] +})); + +const readJSON = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf-8')); + +describe('combineTools function', () => { + const toolsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'tools.json'); + const tagsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'tags.json'); + const manualToolsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'manual-tools.json'); + const automatedToolsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'automated-tools.json'); + + let manualTools; + let automatedTools; + let consoleErrorMock; + + beforeAll(() => { + manualTools = readJSON(manualToolsPath); + automatedTools = readJSON(automatedToolsPath); + }); + + afterAll(() => { + if (fs.existsSync(toolsPath)) fs.unlinkSync(toolsPath); + if (fs.existsSync(tagsPath)) fs.unlinkSync(tagsPath); + + consoleErrorMock.mockRestore(); + }); + + beforeEach(() => { + consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => { }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should combine tools and create correct JSON files', async () => { + await combineTools(automatedTools, manualTools, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + expect(combinedTools).toHaveProperty('category1'); + + const tagsData = readJSON(tagsPath); + expect(tagsData).toHaveProperty('languages'); + expect(tagsData).toHaveProperty('technologies'); + expect(tagsData).toEqual(expectedDataT1) + }); + + it('should handle tools with missing language or technology', async () => { + await combineTools({}, manualToolsWithMissingData, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + expect(combinedTools).toHaveProperty('category1'); + }); + + it('should sort tools alphabetically by title', async () => { + await combineTools(manualToolsToSort, {}, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + const toolTitles = combinedTools.category1.toolsList.map(tool => tool.title); + expect(toolTitles).toEqual(['Tool A', 'Tool Z']); + }); + + it('should log validation errors to console.error', async () => { + await combineTools(automatedToolsT4, manualToolsT4, toolsPath, tagsPath); + + const { message, tool, source, note } = console.error.mock.calls[0][0]; + + expect(message).toBe('Tool validation failed'); + expect(tool).toBe('Invalid Tool'); + expect(source).toBe('manual-tools.json'); + expect(note).toBe('Script continues execution, error logged for investigation'); + + expect(fs.existsSync(toolsPath)).toBe(true); + expect(fs.existsSync(tagsPath)).toBe(true); + }); + + it('should handle tools with multiple languages, including new ones', async () => { + await combineTools(automatedToolsT5, {}, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + const tool = combinedTools.category1.toolsList[0]; + + expect(tool.filters.language).toHaveLength(3); + expect(tool.filters.language).toContainEqual(expect.objectContaining({ name: 'JavaScript' })); + expect(tool.filters.language).toContainEqual(expect.objectContaining({ name: 'Python' })); + expect(tool.filters.language).toContainEqual(expect.objectContaining({ name: 'NewLanguage' })); + + const tagsData = readJSON(tagsPath); + expect(tagsData.languages).toContainEqual(expect.objectContaining({ name: 'NewLanguage' })); + }); + + it('should add a new language and technology when not found in the existing lists', async () => { + await combineTools(automatedToolsT6, {}, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + const tool = combinedTools.category1.toolsList[0]; + + expect(tool.filters.language).toHaveLength(1); + expect(tool.filters.language).toContainEqual(expect.objectContaining({ name: 'NewLanguage' })); + + expect(tool.filters.technology).toHaveLength(1); + expect(tool.filters.technology).toContainEqual(expect.objectContaining({ name: 'NewTechnology' })); + + const tagsData = readJSON(tagsPath); + expect(tagsData.languages).toContainEqual({ + name: 'NewLanguage', + color: 'bg-[#57f281]', + borderColor: 'border-[#37f069]' + }); + expect(tagsData.technologies).toContainEqual({ + name: 'NewTechnology', + color: 'bg-[#61d0f2]', + borderColor: 'border-[#40ccf7]' + }); + }); + + it('should add a new language when it is not found in the existing languages list', async () => { + await combineTools(automatedToolsT7, {}, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + const tool = combinedTools.category1.toolsList[0]; + + expect(tool.filters.language).toHaveLength(1); + expect(tool.filters.language).toContainEqual(expect.objectContaining({ name: 'Go' })); + + const tagsData = readJSON(tagsPath); + expect(tagsData.languages).toContainEqual({ + name: 'Go', + color: 'bg-[#57f281]', + borderColor: 'border-[#37f069]' + }); + }); + + it('should handle valid tool objects', async () => { + await combineTools(automatedToolsT8, manualToolsT8, toolsPath, tagsPath); + + const tagsData = readJSON(tagsPath); + expect(tagsData.languages).toContainEqual({ + name: 'JavaScript', + color: 'bg-[#57f281]', + borderColor: 'border-[#37f069]' + }); + expect(tagsData.technologies).toContainEqual({ + name: 'Node.js', + color: 'bg-[#61d0f2]', + borderColor: 'border-[#40ccf7]' + }); + }); + + it('should handle tool objects without repoUrl', async () => { + await combineTools(automatedToolsT9, manualToolsT9, toolsPath, tagsPath); + + const combinedTools = readJSON(toolsPath); + const tool = combinedTools.category1.toolsList[0]; + + expect(tool.isAsyncAPIrepo).toBeUndefined(); + }); + + it('should throw an error when fs.writeFileSync fails', async () => { + const invalidPath = 'this/is/not/valid'; + await expect(combineTools(automatedTools, manualTools, invalidPath, invalidPath)) + .rejects.toThrow(/ENOENT|EACCES/); + }); + + it('should throw an error when there is an invalid category', async () => { + await expect(combineTools(invalidAutomatedToolsT10, manualTools, toolsPath, tagsPath)) + .rejects.toThrow('Error combining tools'); + }); + + it('should throw an error when URL parsing fails', async () => { + await expect(combineTools(automatedTools, manualToolsWithInvalidURLT11, toolsPath, tagsPath)) + .rejects.toThrow('Invalid URL'); + }); + + it('should handle errors when processing tools with circular references', async () => { + circularTool.circular = circularTool; + await expect(combineTools(automatedToolsT12, {}, toolsPath, tagsPath)) + .rejects.toThrow('Converting circular structure to JSON'); + }); + +}); diff --git a/tests/tools/tools-object.test.js b/tests/tools/tools-object.test.js new file mode 100644 index 000000000000..2577a2a27d94 --- /dev/null +++ b/tests/tools/tools-object.test.js @@ -0,0 +1,161 @@ +const axios = require('axios'); +const { convertTools, createToolObject } = require('../../scripts/tools/tools-object'); +const { + createToolFileContent, + createExpectedToolObject, + createMockData, + createMalformedYAML +} = require('../helper/toolsObjectData'); + +jest.mock('axios'); +jest.mock('../../scripts/tools/categorylist', () => ({ + categoryList: [ + { name: 'Category1', tag: 'Category1', description: 'Description for Category1' }, + { name: 'Others', tag: 'Others', description: 'Other tools category' }, + ] +})); + +describe('Tools Object', () => { + beforeEach(() => { + axios.get.mockClear(); + console.error = jest.fn(); + }); + + const mockToolData = (toolContent, toolNames = ['valid-tool']) => { + const mockData = createMockData(toolNames.map((name) => ({ name: `.asyncapi-tool-${name}`, repoName: name }))); + axios.get.mockResolvedValue({ data: toolContent }); + return mockData; + }; + + it('should create a tool object with provided parameters', async () => { + const toolFile = createToolFileContent({ + title: 'Test Tool', + description: 'Test Description', + hasCommercial: true, + additionalLinks: { docsUrl: 'https://docs.example.com' } + }); + + const expected = createExpectedToolObject({ + title: 'Test Tool', + description: 'Test Description', + hasCommercial: true, + additionalLinks: { docsUrl: 'https://docs.example.com' } + }); + + const result = await createToolObject( + toolFile, + expected.links.repoUrl, + 'Repository Description', + true + ); + + expect(result).toEqual(expected); + }); + + it('should convert tools data correctly', async () => { + const toolContent = createToolFileContent({ title: 'Valid Tool', categories: ['Category1'] }); + const mockData = mockToolData(toolContent); + + const result = await convertTools(mockData); + + expect(result.Category1.toolsList).toHaveLength(1); + expect(result.Category1.toolsList[0].title).toBe('Valid Tool'); + }); + + it('should assign tool to Others category if no matching category is found', async () => { + const toolContent = createToolFileContent({ title: 'Unknown Category Tool', categories: ['UnknownCategory'] }); + const mockData = mockToolData(toolContent); + + const result = await convertTools(mockData); + + expect(result.Others.toolsList).toHaveLength(1); + expect(result.Others.toolsList[0].title).toBe('Unknown Category Tool'); + }); + + it('should log errors for invalid .asyncapi-tool file', async () => { + const invalidContent = createToolFileContent({ + title: 'Invalid Tool', + additionalFilters: { invalidField: true } + }); + const mockData = mockToolData(invalidContent); + + await convertTools(mockData); + + expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Script is not failing')); + expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Invalid .asyncapi-tool file')); + }); + + it('should add duplicate tool objects to the same category', async () => { + const toolContent = createToolFileContent({ + title: 'Duplicate Tool', + categories: ['Category1'] + }); + + const mockData = createMockData([ + { name: '.asyncapi-tool-dup1', repoName: 'dup1' }, + { name: '.asyncapi-tool-dup2', repoName: 'dup2' } + ]); + + axios.get.mockResolvedValue({ data: toolContent }); + + const result = await convertTools(mockData); + + expect(result.Category1.toolsList).toHaveLength(2); + expect(result.Category1.toolsList[0].title).toBe('Duplicate Tool'); + expect(result.Category1.toolsList[1].title).toBe('Duplicate Tool'); + }); + + it('should add tool to Others category only once', async () => { + const toolContent = createToolFileContent({ + title: 'Duplicate Tool in Others', + categories: ['UnknownCategory'] + }); + + const mockData = mockToolData(toolContent); + + const result = await convertTools(mockData); + + expect(result.Others.toolsList).toHaveLength(1); + expect(result.Others.toolsList[0].title).toBe('Duplicate Tool in Others'); + }); + + it('should throw an error if axios.get fails', async () => { + const mockData = createMockData([{ + name: '.asyncapi-tool-error', + repoName: 'error-tool' + }]); + + axios.get.mockRejectedValue(new Error('Network Error')); + + await expect(convertTools(mockData)).rejects.toThrow('Network Error'); + }); + + it('should handle malformed JSON in tool file', async () => { + const malformedContent = createMalformedYAML(); + await expect(convertTools(malformedContent)).rejects.toThrow(); + }); + + it('should use repository description when tool description is missing', async () => { + const toolFile = createToolFileContent({ + title: 'No Description Tool', + description: '', + }); + + const repositoryDescription = 'Fallback Repository Description'; + const mockData = createMockData([{ + name: '.asyncapi-tool-no-description', + repoName: 'no-description', + description: repositoryDescription + }]); + + axios.get.mockResolvedValue({ data: toolFile }); + + const result = await convertTools(mockData); + + const toolObject = result.Category1.toolsList[0]; + + expect(toolObject.description).toBe(repositoryDescription); + expect(toolObject.title).toBe('No Description Tool'); + }); + +});