diff --git a/workspaces/3scale/.changeset/tricky-dogs-float.md b/workspaces/3scale/.changeset/tricky-dogs-float.md new file mode 100644 index 000000000..b9629d3e0 --- /dev/null +++ b/workspaces/3scale/.changeset/tricky-dogs-float.md @@ -0,0 +1,5 @@ +--- +'@backstage-community/plugin-3scale-backend': major +--- + +Merge API docs for the same service. diff --git a/workspaces/3scale/plugins/3scale-backend/package.json b/workspaces/3scale/plugins/3scale-backend/package.json index 73ca1d8d5..983d5d671 100644 --- a/workspaces/3scale/plugins/3scale-backend/package.json +++ b/workspaces/3scale/plugins/3scale-backend/package.json @@ -33,22 +33,27 @@ "postversion": "yarn run export-dynamic", "prepack": "backstage-cli package prepack", "start": "backstage-cli package start", - "test": "backstage-cli package test --passWithNoTests --coverage", - "tsc": "tsc" + "test": "backstage-cli package test --passWithNoTests --coverage" }, "dependencies": { "@backstage/backend-plugin-api": "^1.0.0", "@backstage/catalog-model": "^1.7.0", "@backstage/errors": "^1.2.4", - "@backstage/plugin-catalog-node": "^1.13.0" + "@backstage/plugin-catalog-node": "^1.13.0", + "atlassian-openapi": "^1.0.19", + "openapi-merge": "^1.3.3", + "swagger-converter": "2.1.0", + "swagger2openapi": "^7.0.4" }, "devDependencies": { "@backstage/backend-defaults": "^0.5.0", + "@backstage/backend-test-utils": "0.4.4", "@backstage/cli": "^0.27.1", "@backstage/config": "^1.2.0", "@backstage/plugin-catalog-backend": "^1.26.0", "@janus-idp/cli": "1.13.1", "@types/supertest": "6.0.2", + "@types/swagger2openapi": "^7.0.4", "msw": "1.3.3", "supertest": "7.0.0" }, diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/open-api-3.0-doc-2.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/open-api-3.0-doc-2.json new file mode 100644 index 000000000..6b3703854 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/open-api-3.0-doc-2.json @@ -0,0 +1,199 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0.0", + "title": "Echo API.", + "description": "A sample echo API." + }, + "paths": { + "/": { + "get": { + "description": "Echo API with no parameters", + "operationId": "echo_no_params", + "parameters": [ + { + "name": "user_key", + "in": "query", + "description": "Your API access key", + "required": true, + "x-data-threescale-name": "user_keys", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + } + } + } + } + }, + "/{echo}": { + "get": { + "description": "Echo API with parameters", + "operationId": "echo_with_params", + "parameters": [ + { + "name": "echo", + "in": "path", + "description": "The string to be echoed", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_key", + "in": "query", + "description": "Your API access key", + "required": true, + "x-data-threescale-name": "user_keys", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + } + } + } + } + } + }, + "servers": [ + { + "url": "http://echo-api.3scale.net/" + } + ], + "components": { + "schemas": { + "ResponseModel": { + "type": "object", + "required": ["method", "path", "args", "headers"], + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "args": { + "type": "string" + }, + "headers": { + "type": "object" + } + } + }, + "ErrorModel": { + "type": "object", + "required": ["code", "message"], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/open-api-3.0-doc.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/open-api-3.0-doc.json new file mode 100644 index 000000000..7069c94f3 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/open-api-3.0-doc.json @@ -0,0 +1,88 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Ping Service.", + "description": "A simple API that responds with the input message.", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1", + "description": "Production Server" + }, + { + "url": "https://api.staging.example.com/v1", + "description": "Staging Server" + } + ], + "paths": { + "/ping": { + "post": { + "summary": "Ping message", + "description": "Returns the same message that was sent in the request body.", + "operationId": "pingMessage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + }, + "required": ["message"] + } + } + } + }, + "responses": { + "200": { + "description": "The echoed message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + } + } + } + } + }, + "400": { + "description": "Invalid input, missing 'message' field" + } + } + } + } + }, + "components": { + "schemas": { + "PingRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + }, + "required": ["message"] + }, + "PingResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/swagger-1.2-doc.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/swagger-1.2-doc.json new file mode 100644 index 000000000..ea5717e92 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/swagger-1.2-doc.json @@ -0,0 +1,67 @@ +{ + "apiVersion": "1.2", + "swaggerVersion": "1.2", + "basePath": "https://api.example.com/v1", + "resourcePath": "/profile", + "apis": [ + { + "path": "/profile/{userId}", + "description": "User profile operations", + "operations": [ + { + "method": "GET", + "summary": "Get user profile by user ID", + "notes": "Returns a user's profile details by their ID.", + "type": "User", + "nickname": "getUserProfile", + "parameters": [ + { + "name": "userId", + "description": "ID of the user to fetch", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 200, + "message": "Successful response", + "responseModel": "User" + }, + { + "code": 404, + "message": "User not found" + }, + { + "code": 500, + "message": "Server error" + } + ] + } + ] + } + ], + "models": { + "User": { + "id": "User", + "properties": { + "id": { + "type": "string", + "description": "User ID", + "example": "1001" + }, + "name": { + "type": "string", + "description": "User name", + "example": "John Doe" + }, + "email": { + "type": "string", + "description": "User email address", + "example": "test@example.com" + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/swagger-2.0-doc.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/swagger-2.0-doc.json new file mode 100644 index 000000000..3c687b74e --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/input/swagger-2.0-doc.json @@ -0,0 +1,72 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Simple API.", + "description": "List users API." + }, + "host": "api.example.com", + "basePath": "/v1", + "schemes": ["https"], + "paths": { + "/users": { + "get": { + "summary": "Get all users", + "description": "Returns a list of users.", + "produces": ["application/json"], + "responses": { + "200": { + "description": "A list of users.", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "summary": "Get a user by ID", + "description": "Returns a single user.", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "A single user.", + "schema": { + "$ref": "#/definitions/User" + } + }, + "404": { + "description": "User not found." + } + } + } + } + }, + "definitions": { + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/merged-2-open-api-3.0-docs.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/merged-2-open-api-3.0-docs.json new file mode 100644 index 000000000..2943b0660 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/merged-2-open-api-3.0-docs.json @@ -0,0 +1,268 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "[Merged 2 API docs] Ping Service. Echo API.", + "description": "[Merged 2 API docs] A simple API that responds with the input message. A sample echo API.", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1", + "description": "Production Server" + }, + { + "url": "https://api.staging.example.com/v1", + "description": "Staging Server" + } + ], + "paths": { + "/ping": { + "post": { + "summary": "Ping message", + "description": "Returns the same message that was sent in the request body.", + "operationId": "pingMessage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + }, + "required": ["message"] + } + } + } + }, + "responses": { + "200": { + "description": "The echoed message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + } + } + } + } + }, + "400": { + "description": "Invalid input, missing 'message' field" + } + } + } + }, + "/": { + "get": { + "description": "Echo API with no parameters", + "operationId": "echo_no_params", + "parameters": [ + { + "name": "user_key", + "in": "query", + "description": "Your API access key", + "required": true, + "x-data-threescale-name": "user_keys", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + } + } + } + } + }, + "/{echo}": { + "get": { + "description": "Echo API with parameters", + "operationId": "echo_with_params", + "parameters": [ + { + "name": "echo", + "in": "path", + "description": "The string to be echoed", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "user_key", + "in": "query", + "description": "Your API access key", + "required": true, + "x-data-threescale-name": "user_keys", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ResponseModel" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + }, + "text/html": { + "schema": { + "$ref": "#/components/schemas/ErrorModel" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "PingRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + }, + "required": ["message"] + }, + "PingResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + } + }, + "ResponseModel": { + "type": "object", + "required": ["method", "path", "args", "headers"], + "properties": { + "method": { + "type": "string" + }, + "path": { + "type": "string" + }, + "args": { + "type": "string" + }, + "headers": { + "type": "object" + } + } + }, + "ErrorModel": { + "type": "object", + "required": ["code", "message"], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/merged-3-different-api-docs.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/merged-3-different-api-docs.json new file mode 100644 index 000000000..e85f189b8 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/merged-3-different-api-docs.json @@ -0,0 +1,209 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "[Merged 3 API docs] Ping Service. Simple API. Title was not specified", + "description": "[Merged 3 API docs] A simple API that responds with the input message. List users API.", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1", + "description": "Production Server" + }, + { + "url": "https://api.staging.example.com/v1", + "description": "Staging Server" + } + ], + "paths": { + "/ping": { + "post": { + "summary": "Ping message", + "description": "Returns the same message that was sent in the request body.", + "operationId": "pingMessage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + }, + "required": ["message"] + } + } + } + }, + "responses": { + "200": { + "description": "The echoed message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + } + } + } + } + }, + "400": { + "description": "Invalid input, missing 'message' field" + } + } + } + }, + "/users": { + "get": { + "summary": "Get all users", + "description": "Returns a list of users.", + "responses": { + "200": { + "description": "A list of users.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "summary": "Get a user by ID", + "description": "Returns a single user.", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "A single user.", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "404": { + "description": "User not found." + } + } + } + }, + "/profile/{userId}": { + "get": { + "operationId": "getUserProfile", + "summary": "Get user profile by user ID", + "description": "Returns a user's profile details by their ID.", + "parameters": [ + { + "in": "path", + "description": "ID of the user to fetch", + "name": "userId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/User1" + } + } + } + }, + "404": { + "description": "User not found" + }, + "500": { + "description": "Server error" + } + } + } + } + }, + "components": { + "schemas": { + "PingRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + }, + "required": ["message"] + }, + "PingResponse": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Hello, world!" + } + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "User1": { + "properties": { + "email": { + "type": "string", + "description": "User email address", + "example": "test@example.com" + }, + "id": { + "type": "string", + "description": "User ID", + "example": "1001" + }, + "name": { + "type": "string", + "description": "User name", + "example": "John Doe" + } + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/swagger-1.2-converted-to-swagger-2.0.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/swagger-1.2-converted-to-swagger-2.0.json new file mode 100644 index 000000000..16993328d --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/output/swagger-1.2-converted-to-swagger-2.0.json @@ -0,0 +1,63 @@ +{ + "host": "api.example.com", + "basePath": "/v1", + "schemes": ["https"], + "swagger": "2.0", + "info": { + "title": "Title was not specified", + "version": "1.2" + }, + "paths": { + "/profile/{userId}": { + "get": { + "operationId": "getUserProfile", + "summary": "Get user profile by user ID", + "description": "Returns a user's profile details by their ID.", + "parameters": [ + { + "in": "path", + "description": "ID of the user to fetch", + "name": "userId", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "#/definitions/User" + } + }, + "404": { + "description": "User not found" + }, + "500": { + "description": "Server error" + } + } + } + } + }, + "definitions": { + "User": { + "properties": { + "email": { + "type": "string", + "description": "User email address", + "example": "test@example.com" + }, + "id": { + "type": "string", + "description": "User ID", + "example": "1001" + }, + "name": { + "type": "string", + "description": "User name", + "example": "John Doe" + } + } + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/proxy.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/proxy.json new file mode 100644 index 000000000..52896afa9 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/proxy.json @@ -0,0 +1,53 @@ +{ + "proxy": { + "service_id": 2, + "endpoint": "https://api-3scale-apicast-production.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com:443", + "api_backend": "https://echo-api.3scale.net:443", + "credentials_location": "query", + "auth_app_key": "app_key", + "auth_app_id": "app_id", + "auth_user_key": "user_key", + "error_auth_failed": "Authentication failed", + "error_auth_missing": "Authentication parameters missing", + "error_status_auth_failed": 403, + "error_headers_auth_failed": "text/plain; charset=us-ascii", + "error_status_auth_missing": 403, + "error_headers_auth_missing": "text/plain; charset=us-ascii", + "error_no_match": "No Mapping Rule matched", + "error_status_no_match": 404, + "error_headers_no_match": "text/plain; charset=us-ascii", + "error_limits_exceeded": "Usage limit exceeded", + "error_status_limits_exceeded": 429, + "error_headers_limits_exceeded": "text/plain; charset=us-ascii", + "secret_token": "Shared_secret_sent_from_proxy_to_API_backend_60e69c4e8b2d7666", + "sandbox_endpoint": "https://api-3scale-apicast-staging.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com:443", + "api_test_path": "/", + "api_test_success": true, + "policies_config": [ + { + "name": "apicast", + "version": "builtin", + "configuration": {}, + "enabled": true + } + ], + "created_at": "2024-09-17T09:57:53Z", + "updated_at": "2024-09-17T10:06:42Z", + "deployment_option": "hosted", + "lock_version": 1, + "links": [ + { + "rel": "mapping_rules", + "href": "/admin/api/services/2/proxy/mapping_rules" + }, + { + "rel": "self", + "href": "/admin/api/services/2/proxy" + }, + { + "rel": "service", + "href": "/admin/api/services/2" + } + ] + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/services.json b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/services.json new file mode 100644 index 000000000..67523a131 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/__fixtures__/data/services.json @@ -0,0 +1,49 @@ +{ + "services": [ + { + "service": { + "id": 2, + "name": "API", + "description": "Nice API", + "state": "incomplete", + "system_name": "api", + "backend_version": "1", + "deployment_option": "hosted", + "support_email": "admin@3scale.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com", + "intentions_required": false, + "buyers_manage_apps": true, + "buyers_manage_keys": true, + "referrer_filters_required": false, + "custom_keys_enabled": true, + "buyer_key_regenerate_enabled": true, + "mandatory_app_key": true, + "buyer_can_select_plan": false, + "buyer_plan_change_permission": "request", + "created_at": "2024-09-17T09:57:53Z", + "updated_at": "2024-09-17T10:06:42Z", + "links": [ + { + "rel": "metrics", + "href": "https://3scale-admin.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com/admin/api/services/2/metrics" + }, + { + "rel": "self", + "href": "https://3scale-admin.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com/admin/api/services/2" + }, + { + "rel": "service_plans", + "href": "https://3scale-admin.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com/admin/api/services/2/service_plans" + }, + { + "rel": "application_plans", + "href": "https://3scale-admin.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com/admin/api/services/2/application_plans" + }, + { + "rel": "features", + "href": "https://3scale-admin.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com/admin/api/services/2/features" + } + ] + } + } + ] +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.test.ts b/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.test.ts new file mode 100644 index 000000000..63c6eeba4 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.test.ts @@ -0,0 +1,356 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { ThreeScaleApiEntityProvider } from './ThreeScaleApiEntityProvider'; +import { ConfigReader } from '@backstage/config'; +import { + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; +import { resolve } from 'path'; +import fs from 'fs'; + +const requestJsonDataMock = jest.fn().mockResolvedValue([]); + +global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve(requestJsonDataMock()), + headers: new Headers(), + text: () => Promise.resolve('mocked text data'), + } as Response), +); + +const loggerMock = mockServices.logger.mock(); + +const schedulerTaskRunnerMock = { + run: jest.fn().mockImplementation(), +}; + +const shedulerServiceMock = { + triggerTask: jest.fn().mockImplementation(), + scheduleTask: jest.fn().mockImplementation(), + createScheduledTaskRunner: jest.fn().mockImplementation(), + getScheduledTasks: jest.fn().mockImplementation(), +}; + +const entityProviderConnection = { + applyMutation: jest.fn().mockImplementation(), + refresh: jest.fn().mockImplementation(), +}; + +describe('ThreeScaleApiEntityProvider', () => { + let conf: ConfigReader; + + beforeEach(() => { + conf = new ConfigReader({ + catalog: { + providers: { + threeScaleApiEntity: { + test: { + baseUrl: 'test', + accessToken: 'test', + }, + }, + }, + }, + }); + }); + + function createApiEntityProvider( + schedule: SchedulerServiceTaskRunner, + scheduler: SchedulerService, + ): ThreeScaleApiEntityProvider[] { + return ThreeScaleApiEntityProvider.fromConfig( + { config: conf, logger: loggerMock }, + { schedule, scheduler }, + ); + } + + it('should be defined', () => { + const threeScaleApiEntityProvider = createApiEntityProvider( + schedulerTaskRunnerMock, + shedulerServiceMock, + ); + expect(threeScaleApiEntityProvider).toBeDefined(); + expect(threeScaleApiEntityProvider).toBeInstanceOf(Array); + expect(threeScaleApiEntityProvider.length).toBe(1); + }); + + describe('run', () => { + let threeScaleApiEntityProvider: ThreeScaleApiEntityProvider; + beforeEach(async () => { + entityProviderConnection.applyMutation.mockClear(); + threeScaleApiEntityProvider = createApiEntityProvider( + schedulerTaskRunnerMock, + shedulerServiceMock, + )[0]; + await threeScaleApiEntityProvider.connect(entityProviderConnection); + }); + + it('should be created catalog entity with single open API 3.0 doc', async () => { + const services = readTestJSONFile('services'); + requestJsonDataMock.mockResolvedValueOnce(services); + + const openAPI3_0Spec = readTestJSONFile('input/open-api-3.0-doc'); + const apiDoc = createAPIDoc( + 'ping', + 'ping', + 'A simple API that responds with the input message.', + openAPI3_0Spec, + ); + const apiDocs = { api_docs: [apiDoc] }; + requestJsonDataMock.mockResolvedValueOnce(apiDocs); + + const proxy = readTestJSONFile('proxy'); + requestJsonDataMock.mockResolvedValueOnce(proxy); + + await threeScaleApiEntityProvider.run(); + + const entities = [ + createExpectedEntity( + 'input/open-api-3.0-doc', + 'A simple API that responds with the input message.', + ), + ]; + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities, + }); + }); + + it('should be created catalog entity with single api doc but swagger 2.0 should not be converted to API 3.0', async () => { + const services = readTestJSONFile('services'); + requestJsonDataMock.mockResolvedValueOnce(services); + + const swagger2_0Spec = readTestJSONFile('input/swagger-2.0-doc'); + const apiDoc = createAPIDoc( + 'list-users', + 'List users API', + 'List users API.', + swagger2_0Spec, + ); + const apiDocs = { api_docs: [apiDoc] }; + requestJsonDataMock.mockResolvedValueOnce(apiDocs); + + const proxy = readTestJSONFile('proxy'); + requestJsonDataMock.mockResolvedValueOnce(proxy); + + await threeScaleApiEntityProvider.run(); + + const entities = [ + createExpectedEntity('input/swagger-2.0-doc', 'List users API.'), + ]; + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities, + }); + }); + + it('should be created catalog entity with single api doc but converted from swagger 1.2 to swagger 2.0', async () => { + const services = readTestJSONFile('services'); + requestJsonDataMock.mockResolvedValueOnce(services); + const swagger1_2Spec = readTestJSONFile('input/swagger-1.2-doc'); + const apiDoc = createAPIDoc( + 'get-user-profile-by-id', + 'Get User Profile By ID', + 'User profile API.', + swagger1_2Spec, + ); + const apiDocs = { api_docs: [apiDoc] }; + requestJsonDataMock.mockResolvedValueOnce(apiDocs); + const proxy = readTestJSONFile('proxy'); + requestJsonDataMock.mockResolvedValueOnce(proxy); + + await threeScaleApiEntityProvider.run(); + + const entities = [ + createExpectedEntity( + 'output/swagger-1.2-converted-to-swagger-2.0', + 'Nice API', + ), + ]; + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities, + }); + }); + + it('should be created catalog entity with merged 2 open API 3.0 docs', async () => { + const services = readTestJSONFile('services'); + requestJsonDataMock.mockResolvedValueOnce(services); + + const openAPI3_0Spec1 = readTestJSONFile('input/open-api-3.0-doc'); + const apiDoc1 = createAPIDoc( + 'ping', + 'Ping', + 'A simple API that responds with the input message.', + openAPI3_0Spec1, + ); + const openAPI3_0Spec2 = readTestJSONFile('input/open-api-3.0-doc-2'); + const apiDoc2 = createAPIDoc( + 'echo', + 'Echo', + 'A sample echo API.', + openAPI3_0Spec2, + ); + + const apiDocs = { api_docs: [apiDoc1, apiDoc2] }; + + requestJsonDataMock.mockResolvedValueOnce(apiDocs); + + const proxy = readTestJSONFile('proxy'); + requestJsonDataMock.mockResolvedValueOnce(proxy); + + await threeScaleApiEntityProvider.run(); + + const entities = [ + createExpectedEntity( + 'output/merged-2-open-api-3.0-docs', + '[Merged 2 API docs] A simple API that responds with the input message. A sample echo API.', + ), + ]; + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities, + }); + }); + + it('should be created catalog entity with merged 3 api docs in different formats', async () => { + const services = readTestJSONFile('services'); + requestJsonDataMock.mockResolvedValueOnce(services); + + const openAPI3_0Spec1 = readTestJSONFile('input/open-api-3.0-doc'); + const apiDoc1 = createAPIDoc( + 'ping', + 'ping', + 'A simple API that responds with the input message.', + openAPI3_0Spec1, + ); + const swagger2_0Spec = readTestJSONFile('input/swagger-2.0-doc'); + const apiDoc2 = createAPIDoc( + 'list-users', + 'List users API', + 'List users API.', + swagger2_0Spec, + ); + const swagger1_2Spec = readTestJSONFile('input/swagger-1.2-doc'); + const apiDoc3 = createAPIDoc( + 'get-user-profile-by-id', + 'Get User Profile By ID', + 'User profile API.', + swagger1_2Spec, + ); + + const apiDocs = { api_docs: [apiDoc1, apiDoc2, apiDoc3] }; + + requestJsonDataMock.mockResolvedValueOnce(apiDocs); + + const proxy = readTestJSONFile('proxy'); + requestJsonDataMock.mockResolvedValueOnce(proxy); + + await threeScaleApiEntityProvider.run(); + + const entities = [ + createExpectedEntity( + 'output/merged-3-different-api-docs', + '[Merged 3 API docs] A simple API that responds with the input message. List users API.', + ), + ]; + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities, + }); + }); + }); +}); + +function readTestJSONFile(fileName: string): any { + const file = resolve(__dirname, `./../__fixtures__/data/${fileName}.json`); + const fileContent = fs.readFileSync(file, 'utf8'); + return JSON.parse(fileContent); +} + +function createExpectedEntity( + fileWithExpectedOpenAPISpec: string, + description: string, +): any { + return { + entity: { + kind: 'API', + apiVersion: 'backstage.io/v1alpha1', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'url:test/apiconfig/services/2', + 'backstage.io/managed-by-origin-location': + 'url:test/apiconfig/services/2', + }, + name: 'api', + description, + links: [ + { + url: 'test/apiconfig/services/2', + title: '3scale Overview', + }, + { + url: 'https://api-3scale-apicast-staging.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com:443', + title: 'Staging Apicast Endpoint', + }, + { + url: 'https://api-3scale-apicast-production.apps.rosa.ceazq-ocd3j-vy9.8aja.p3.openshiftapps.com:443', + title: 'Production Apicast Endpoint', + }, + ], + }, + spec: { + type: 'openapi', + lifecycle: 'test', + system: '3scale', + owner: '3scale', + definition: JSON.stringify( + readTestJSONFile(fileWithExpectedOpenAPISpec), + null, + 2, + ), + }, + }, + locationKey: 'ThreeScaleApiEntityProvider:test', + }; +} + +function createAPIDoc( + systemName: string, + name: string, + description: string, + apiDocBody: any, +) { + return { + api_doc: { + id: 1, + system_name: systemName, + name: name, + description: description, + published: true, + skip_swagger_validations: false, + body: JSON.stringify(apiDocBody), + service_id: 2, + created_at: '2024-09-17T10:09:04Z', + updated_at: '2024-09-17T10:09:04Z', + }, + }; +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.ts b/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.ts index 0fcbbcaeb..b57b95022 100644 --- a/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.ts +++ b/workspaces/3scale/plugins/3scale-backend/src/providers/ThreeScaleApiEntityProvider.ts @@ -19,7 +19,6 @@ import type { SchedulerService, LoggerService, } from '@backstage/backend-plugin-api'; - import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -48,7 +47,14 @@ import type { Services, } from '../clients/types'; import { readThreeScaleApiEntityConfigs } from './config'; -import type { ThreeScaleConfig } from './types'; +import { isNonEmptyArray, NonEmptyArray, ThreeScaleConfig } from './types'; +import { + isOpenAPI3_0, + isSwagger1_2, + isSwagger2_0, + OpenAPIMergerAndConverter, +} from './open-api-merger-converter'; +import { Swagger } from 'atlassian-openapi'; export class ThreeScaleApiEntityProvider implements EntityProvider { private static SERVICES_FETCH_SIZE: number = 500; @@ -57,6 +63,7 @@ export class ThreeScaleApiEntityProvider implements EntityProvider { private readonly accessToken: string; private readonly logger: LoggerService; private readonly scheduleFn: () => Promise; + private readonly openApiMerger: OpenAPIMergerAndConverter; private connection?: EntityProviderConnection; static fromConfig( @@ -119,6 +126,7 @@ export class ThreeScaleApiEntityProvider implements EntityProvider { }); this.scheduleFn = this.createScheduleFn(taskRunner); + this.openApiMerger = new OpenAPIMergerAndConverter(); } private createScheduleFn( @@ -186,24 +194,19 @@ export class ThreeScaleApiEntityProvider implements EntityProvider { const service = element; this.logger.debug(`Find service ${service.service.name}`); - // Trying to find the API Doc for the service and validate if api doc was assigned to an API. - const apiDoc = apiDocs.api_docs.find(obj => { - if (obj.api_doc.service_id !== undefined) { - return obj.api_doc.service_id === service.service.id; - } - return false; - }); - + const docs = apiDocs.api_docs.filter( + obj => obj.api_doc.service_id === service.service.id, + ); const proxy = await getProxyConfig( this.baseUrl, this.accessToken, service.service.id, ); - if (apiDoc !== undefined) { - this.logger.info(JSON.stringify(apiDoc)); - const apiEntity: ApiEntity = this.buildApiEntityFromService( + if (isNonEmptyArray(docs)) { + this.logger.info(JSON.stringify(docs)); + const apiEntity: ApiEntity = await this.buildApiEntityFromService( service, - apiDoc, + docs, proxy, ); entities.push(apiEntity); @@ -231,14 +234,55 @@ export class ThreeScaleApiEntityProvider implements EntityProvider { }); } - private buildApiEntityFromService( + private async buildApiEntityFromService( service: ServiceElement, - apiDoc: APIDocElement, + apiDocs: NonEmptyArray, proxy: Proxy, - ): ApiEntity { + ): Promise { const location = `url:${this.baseUrl}/apiconfig/services/${service.service.id}`; + const serviceDescription = service.service.description || ''; + let entityDescription: string | undefined; - const spec = JSON.parse(apiDoc.api_doc.body); + const docs = apiDocs.map(doc => JSON.parse(doc.api_doc.body)); + + let swaggerDocJSON; + if (docs.length > 1) { + // convert all docs to openapi 3.0 and merge them + let mergedDescription = `[Merged ${docs.length} API docs]`; + let mergedTitle = mergedDescription; + const convertedDocs: Swagger.SwaggerV3[] = []; + for (const doc of docs) { + const convertedDoc = await this.openApiMerger.convertAPIDocToOpenAPI3( + doc, + ); + convertedDocs.push(convertedDoc); + mergedDescription = getDocInfo(convertedDoc)?.description + ? `${mergedDescription} ${getDocInfo(convertedDoc)?.description}` + : mergedDescription; + mergedTitle = getDocInfo(convertedDoc)?.title + ? `${mergedTitle} ${getDocInfo(convertedDoc)?.title}` + : mergedTitle; + } + if (isNonEmptyArray(convertedDocs)) { + swaggerDocJSON = await this.openApiMerger.mergeOpenAPI3Docs( + convertedDocs, + ); + swaggerDocJSON.info.description = mergedDescription; + swaggerDocJSON.info.title = mergedTitle; + entityDescription = mergedDescription; + } + } + + if (docs.length === 1) { + swaggerDocJSON = docs[0]; + + const spec = JSON.parse(apiDocs[0].api_doc.body); + if (isSwagger1_2(spec)) { + // Backstage UI can render only openapi 3.0 or swagger 2.0. That's why we need to convert swagger 1.2 to swagger 2.0. + swaggerDocJSON = await this.openApiMerger.convertSwagger1_2To2_0(spec); + } + entityDescription = getDocInfo(spec)?.description; + } return { kind: 'API', @@ -250,8 +294,7 @@ export class ThreeScaleApiEntityProvider implements EntityProvider { }, // TODO: add tenant name name: `${service.service.system_name}`, - description: - spec.info.description || `Version: ${service.service.description}`, + description: entityDescription || serviceDescription, // TODO: add labels // labels: this.getApiEntityLabels(service), links: [ @@ -274,8 +317,19 @@ export class ThreeScaleApiEntityProvider implements EntityProvider { lifecycle: this.env, system: '3scale', owner: '3scale', - definition: apiDoc.api_doc.body, + definition: JSON.stringify(swaggerDocJSON, null, 2), }, }; } } + +function getDocInfo( + spec: any, +): { description: string; title: string } | undefined { + if (isSwagger2_0(spec) || isOpenAPI3_0(spec)) { + return spec.info; + } + + // swagger 1.2 spec doc defined by single file doesn't have description field + return undefined; +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/providers/open-api-merger-converter.ts b/workspaces/3scale/plugins/3scale-backend/src/providers/open-api-merger-converter.ts new file mode 100644 index 000000000..aeb4993d9 --- /dev/null +++ b/workspaces/3scale/plugins/3scale-backend/src/providers/open-api-merger-converter.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { merge, isErrorResult, MergeInput } from 'openapi-merge'; +import { Swagger } from 'atlassian-openapi'; +import Swagger2OpenAPI from 'swagger2openapi'; +// @ts-ignore +import SwaggerConverter from 'swagger-converter'; +import { NonEmptyArray } from './types'; + +export function isSwagger1_2(apiDoc: any): boolean { + return apiDoc.swaggerVersion && apiDoc.swaggerVersion === '1.2'; +} + +export function isSwagger2_0(apiDoc: any): boolean { + return apiDoc.swagger && apiDoc.swagger === '2.0'; +} + +export function isOpenAPI3_0(apiDoc: any): boolean { + return apiDoc.openapi; +} + +export class OpenAPIMergerAndConverter { + async mergeOpenAPI3Docs( + docs: NonEmptyArray, + ): Promise { + const mergeInput: MergeInput = docs.map(doc => { + return { oas: doc }; + }); + + const result = await merge(mergeInput); + if (isErrorResult(result)) { + throw new Error(result.message); + } + return result.output; + } + + // Convert api doc to format openAPI 3. Do nothing with doc if it has format openAPI 3.0. + // 3scale supports API docs in formats: + // - swagger 1.2 + // - swagger 2.0 + // - openAPI 3.0 + async convertAPIDocToOpenAPI3(apiDoc: any): Promise { + if (isOpenAPI3_0(apiDoc)) { + return apiDoc; + } + if (isSwagger1_2(apiDoc)) { + // Unfortunately there is no library in the JavaScript world, which can convert both swagger 1.2 and 2.0 to openAPI 3.0. + // That's why, for swagger 1.2 we are using convertation to swagger 2.0. And then swagger 2.0 will be converted to openAPI 3.0. + const swagger2_0Doc = await this.convertSwagger1_2To2_0(apiDoc); + return await this.convertSwagger2_0ToOpenAPI3_0(swagger2_0Doc); + } + if (isSwagger2_0(apiDoc)) { + return await this.convertSwagger2_0ToOpenAPI3_0(apiDoc); + } + + throw new Error( + `Unsupported API document. Plugin supports Swagger 1.2, 2.0, 3.0(Open API 3.0)`, + ); + } + + async convertSwagger1_2To2_0(swaggerDoc: any): Promise { + try { + const result = SwaggerConverter.convert(swaggerDoc, {}); + return result; + } catch (error) { + console.error('Error converting Swagger 1.2 to Swagger 2.0:', error); + throw error; + } + } + + private async convertSwagger2_0ToOpenAPI3_0(swaggerDoc: any): Promise { + try { + const result = await Swagger2OpenAPI.convertObj(swaggerDoc, { + patch: true, // patch: true helps to fix minor issues + warnOnly: true, // Do not throw on non-patchable errors + }); + return result.openapi; + } catch (error) { + console.error('Error converting Swagger 2.0 to OpenAPI 3.0:', error); + throw error; + } + } +} diff --git a/workspaces/3scale/plugins/3scale-backend/src/providers/types.ts b/workspaces/3scale/plugins/3scale-backend/src/providers/types.ts index 493df3341..466248166 100644 --- a/workspaces/3scale/plugins/3scale-backend/src/providers/types.ts +++ b/workspaces/3scale/plugins/3scale-backend/src/providers/types.ts @@ -25,3 +25,9 @@ export type ThreeScaleConfig = { addLabels?: boolean; schedule?: SchedulerServiceTaskScheduleDefinition; }; + +export type NonEmptyArray = [T, ...T[]]; + +export function isNonEmptyArray(arr: T[]): arr is NonEmptyArray { + return arr.length > 0; +} diff --git a/workspaces/3scale/yarn.lock b/workspaces/3scale/yarn.lock index f866da83f..21a97571c 100644 --- a/workspaces/3scale/yarn.lock +++ b/workspaces/3scale/yarn.lock @@ -2380,6 +2380,7 @@ __metadata: dependencies: "@backstage/backend-defaults": ^0.5.0 "@backstage/backend-plugin-api": ^1.0.0 + "@backstage/backend-test-utils": 0.4.4 "@backstage/catalog-model": ^1.7.0 "@backstage/cli": ^0.27.1 "@backstage/config": ^1.2.0 @@ -2388,11 +2389,105 @@ __metadata: "@backstage/plugin-catalog-node": ^1.13.0 "@janus-idp/cli": 1.13.1 "@types/supertest": 6.0.2 + "@types/swagger2openapi": ^7.0.4 + atlassian-openapi: ^1.0.19 msw: 1.3.3 + openapi-merge: ^1.3.3 supertest: 7.0.0 + swagger-converter: 2.1.0 + swagger2openapi: ^7.0.4 languageName: unknown linkType: soft +"@backstage/backend-app-api@npm:^0.8.0": + version: 0.8.0 + resolution: "@backstage/backend-app-api@npm:0.8.0" + dependencies: + "@backstage/backend-common": ^0.23.3 + "@backstage/backend-plugin-api": ^0.7.0 + "@backstage/backend-tasks": ^0.5.27 + "@backstage/cli-common": ^0.1.14 + "@backstage/cli-node": ^0.2.7 + "@backstage/config": ^1.2.0 + "@backstage/config-loader": ^1.8.1 + "@backstage/errors": ^1.2.4 + "@backstage/plugin-auth-node": ^0.4.17 + "@backstage/plugin-permission-node": ^0.8.0 + "@backstage/types": ^1.1.1 + "@manypkg/get-packages": ^1.1.3 + "@types/cors": ^2.8.6 + "@types/express": ^4.17.6 + compression: ^1.7.4 + cookie: ^0.6.0 + cors: ^2.8.5 + express: ^4.17.1 + express-promise-router: ^4.1.0 + fs-extra: ^11.2.0 + helmet: ^6.0.0 + jose: ^5.0.0 + knex: ^3.0.0 + lodash: ^4.17.21 + logform: ^2.3.2 + luxon: ^3.0.0 + minimatch: ^9.0.0 + minimist: ^1.2.5 + morgan: ^1.10.0 + node-fetch: ^2.6.7 + node-forge: ^1.3.1 + path-to-regexp: ^6.2.1 + selfsigned: ^2.0.0 + stoppable: ^1.1.0 + triple-beam: ^1.4.1 + uuid: ^9.0.0 + winston: ^3.2.1 + winston-transport: ^4.5.0 + checksum: 663b0517e7d4c948d005c2120a83f7720ea02e68ad9600b5e5a3b22441a23b70523ebaf725b0598c7a1916c6a36261367d8316cfa1686a09955bdfdf457497d6 + languageName: node + linkType: hard + +"@backstage/backend-app-api@npm:^0.9.3": + version: 0.9.3 + resolution: "@backstage/backend-app-api@npm:0.9.3" + dependencies: + "@backstage/backend-common": ^0.24.1 + "@backstage/backend-plugin-api": ^0.8.1 + "@backstage/cli-common": ^0.1.14 + "@backstage/cli-node": ^0.2.7 + "@backstage/config": ^1.2.0 + "@backstage/config-loader": ^1.9.0 + "@backstage/errors": ^1.2.4 + "@backstage/plugin-auth-node": ^0.5.1 + "@backstage/plugin-permission-node": ^0.8.2 + "@backstage/types": ^1.1.1 + "@manypkg/get-packages": ^1.1.3 + compression: ^1.7.4 + cookie: ^0.6.0 + cors: ^2.8.5 + express: ^4.17.1 + express-promise-router: ^4.1.0 + fs-extra: ^11.2.0 + helmet: ^6.0.0 + jose: ^5.0.0 + knex: ^3.0.0 + lodash: ^4.17.21 + logform: ^2.3.2 + luxon: ^3.0.0 + minimatch: ^9.0.0 + minimist: ^1.2.5 + morgan: ^1.10.0 + node-fetch: ^2.7.0 + node-forge: ^1.3.1 + path-to-regexp: ^6.2.1 + selfsigned: ^2.0.0 + stoppable: ^1.1.0 + triple-beam: ^1.4.1 + uuid: ^9.0.0 + winston: ^3.2.1 + winston-transport: ^4.5.0 + checksum: 42c41bf9c71bcd5a34675e9116879949693f388d74147a6f1c8ad4b483799ee03be999b5d431547f0ab3f3e24cc9a7a813315fc627b2ba4af961f99b237e9dd5 + languageName: node + linkType: hard + "@backstage/backend-app-api@npm:^1.0.0": version: 1.0.0 resolution: "@backstage/backend-app-api@npm:1.0.0" @@ -2510,6 +2605,83 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-common@npm:^0.24.1": + version: 0.24.1 + resolution: "@backstage/backend-common@npm:0.24.1" + dependencies: + "@aws-sdk/abort-controller": ^3.347.0 + "@aws-sdk/client-codecommit": ^3.350.0 + "@aws-sdk/client-s3": ^3.350.0 + "@aws-sdk/credential-providers": ^3.350.0 + "@aws-sdk/types": ^3.347.0 + "@backstage/backend-dev-utils": ^0.1.5 + "@backstage/backend-plugin-api": ^0.8.1 + "@backstage/cli-common": ^0.1.14 + "@backstage/config": ^1.2.0 + "@backstage/config-loader": ^1.9.0 + "@backstage/errors": ^1.2.4 + "@backstage/integration": ^1.14.0 + "@backstage/integration-aws-node": ^0.1.12 + "@backstage/plugin-auth-node": ^0.5.1 + "@backstage/types": ^1.1.1 + "@google-cloud/storage": ^7.0.0 + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 + "@kubernetes/client-node": 0.20.0 + "@manypkg/get-packages": ^1.1.3 + "@octokit/rest": ^19.0.3 + "@types/cors": ^2.8.6 + "@types/dockerode": ^3.3.0 + "@types/express": ^4.17.6 + "@types/luxon": ^3.0.0 + "@types/webpack-env": ^1.15.2 + archiver: ^6.0.0 + base64-stream: ^1.0.0 + compression: ^1.7.4 + concat-stream: ^2.0.0 + cors: ^2.8.5 + dockerode: ^4.0.0 + express: ^4.17.1 + express-promise-router: ^4.1.0 + fs-extra: ^11.2.0 + git-url-parse: ^14.0.0 + helmet: ^6.0.0 + isomorphic-git: ^1.23.0 + jose: ^5.0.0 + keyv: ^4.5.2 + knex: ^3.0.0 + lodash: ^4.17.21 + logform: ^2.3.2 + luxon: ^3.0.0 + minimatch: ^9.0.0 + minimist: ^1.2.5 + morgan: ^1.10.0 + mysql2: ^3.0.0 + node-fetch: ^2.7.0 + node-forge: ^1.3.1 + p-limit: ^3.1.0 + path-to-regexp: ^6.2.1 + pg: ^8.11.3 + pg-format: ^1.0.4 + raw-body: ^2.4.1 + selfsigned: ^2.0.0 + stoppable: ^1.1.0 + tar: ^6.1.12 + triple-beam: ^1.4.1 + uuid: ^9.0.0 + winston: ^3.2.1 + winston-transport: ^4.5.0 + yauzl: ^3.0.0 + yn: ^4.0.0 + peerDependencies: + pg-connection-string: ^2.3.0 + peerDependenciesMeta: + pg-connection-string: + optional: true + checksum: 5a326dec02d1d43a819e5b1d4a0be87ee00be23013e4a74c7c700f996d3b486d359ba2866c20a25788a9eef6a97d2f99b5ccbb140bcac3180d703eee6ad82c04 + languageName: node + linkType: hard + "@backstage/backend-common@npm:^0.25.0": version: 0.25.0 resolution: "@backstage/backend-common@npm:0.25.0" @@ -2587,6 +2759,83 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-defaults@npm:^0.4.0": + version: 0.4.4 + resolution: "@backstage/backend-defaults@npm:0.4.4" + dependencies: + "@aws-sdk/abort-controller": ^3.347.0 + "@aws-sdk/client-codecommit": ^3.350.0 + "@aws-sdk/client-s3": ^3.350.0 + "@aws-sdk/credential-providers": ^3.350.0 + "@aws-sdk/types": ^3.347.0 + "@backstage/backend-app-api": ^0.9.3 + "@backstage/backend-common": ^0.24.1 + "@backstage/backend-dev-utils": ^0.1.5 + "@backstage/backend-plugin-api": ^0.8.1 + "@backstage/cli-common": ^0.1.14 + "@backstage/config": ^1.2.0 + "@backstage/config-loader": ^1.9.0 + "@backstage/errors": ^1.2.4 + "@backstage/integration": ^1.14.0 + "@backstage/integration-aws-node": ^0.1.12 + "@backstage/plugin-auth-node": ^0.5.1 + "@backstage/plugin-events-node": ^0.3.10 + "@backstage/plugin-permission-node": ^0.8.2 + "@backstage/types": ^1.1.1 + "@google-cloud/storage": ^7.0.0 + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 + "@manypkg/get-packages": ^1.1.3 + "@octokit/rest": ^19.0.3 + "@opentelemetry/api": ^1.3.0 + "@types/cors": ^2.8.6 + "@types/express": ^4.17.6 + archiver: ^6.0.0 + base64-stream: ^1.0.0 + better-sqlite3: ^11.0.0 + compression: ^1.7.4 + concat-stream: ^2.0.0 + cookie: ^0.6.0 + cors: ^2.8.5 + cron: ^3.0.0 + express: ^4.17.1 + express-promise-router: ^4.1.0 + fs-extra: ^11.2.0 + git-url-parse: ^14.0.0 + helmet: ^6.0.0 + isomorphic-git: ^1.23.0 + jose: ^5.0.0 + keyv: ^4.5.2 + knex: ^3.0.0 + lodash: ^4.17.21 + logform: ^2.3.2 + luxon: ^3.0.0 + minimatch: ^9.0.0 + minimist: ^1.2.5 + morgan: ^1.10.0 + mysql2: ^3.0.0 + node-fetch: ^2.7.0 + node-forge: ^1.3.1 + p-limit: ^3.1.0 + path-to-regexp: ^6.2.1 + pg: ^8.11.3 + pg-connection-string: ^2.3.0 + pg-format: ^1.0.4 + raw-body: ^2.4.1 + selfsigned: ^2.0.0 + stoppable: ^1.1.0 + tar: ^6.1.12 + triple-beam: ^1.4.1 + uuid: ^9.0.0 + winston: ^3.2.1 + winston-transport: ^4.5.0 + yauzl: ^3.0.0 + yn: ^4.0.0 + zod: ^3.22.4 + checksum: 07f02b0eed51b5741b8e32f3026b8a2a29c8598a60bd20c4c4ae98dcdf849321f55672266ae50de9f28fb6f1e9c65409d8a76760ca405af175739eb00a01be21 + languageName: node + linkType: hard + "@backstage/backend-defaults@npm:^0.5.0": version: 0.5.0 resolution: "@backstage/backend-defaults@npm:0.5.0" @@ -2710,6 +2959,25 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-plugin-api@npm:^0.8.1": + version: 0.8.1 + resolution: "@backstage/backend-plugin-api@npm:0.8.1" + dependencies: + "@backstage/cli-common": ^0.1.14 + "@backstage/config": ^1.2.0 + "@backstage/errors": ^1.2.4 + "@backstage/plugin-auth-node": ^0.5.1 + "@backstage/plugin-permission-common": ^0.8.1 + "@backstage/types": ^1.1.1 + "@types/express": ^4.17.6 + "@types/luxon": ^3.0.0 + express: ^4.17.1 + knex: ^3.0.0 + luxon: ^3.0.0 + checksum: 4a6614ceec13ff5ace3e04e8a1bad40567ce6a66afc19c02935161d12bdd7edbf4863d1d0203539e8a353dd78184078eb873fd14da6cbd093d1d9e4ced44c0fb + languageName: node + linkType: hard + "@backstage/backend-plugin-api@npm:^1.0.0": version: 1.0.0 resolution: "@backstage/backend-plugin-api@npm:1.0.0" @@ -2729,6 +2997,62 @@ __metadata: languageName: node linkType: hard +"@backstage/backend-tasks@npm:^0.5.27": + version: 0.5.27 + resolution: "@backstage/backend-tasks@npm:0.5.27" + dependencies: + "@backstage/backend-common": ^0.23.3 + "@backstage/backend-plugin-api": ^0.7.0 + "@backstage/config": ^1.2.0 + "@backstage/errors": ^1.2.4 + "@backstage/types": ^1.1.1 + "@opentelemetry/api": ^1.3.0 + "@types/luxon": ^3.0.0 + cron: ^3.0.0 + knex: ^3.0.0 + lodash: ^4.17.21 + luxon: ^3.0.0 + uuid: ^9.0.0 + zod: ^3.22.4 + checksum: 69afa09bb380cdc93d52bf4e93b94a4aa8b3c9ef74f3e4350a6beedbbc623095805c0613f691a42a3995795fe0c9f9ccce689ce8c2f3a11277534d13ac4aa2a6 + languageName: node + linkType: hard + +"@backstage/backend-test-utils@npm:0.4.4": + version: 0.4.4 + resolution: "@backstage/backend-test-utils@npm:0.4.4" + dependencies: + "@backstage/backend-app-api": ^0.8.0 + "@backstage/backend-defaults": ^0.4.0 + "@backstage/backend-plugin-api": ^0.7.0 + "@backstage/config": ^1.2.0 + "@backstage/errors": ^1.2.4 + "@backstage/plugin-auth-node": ^0.4.17 + "@backstage/plugin-events-node": ^0.3.8 + "@backstage/types": ^1.1.1 + "@keyv/memcache": ^1.3.5 + "@keyv/redis": ^2.5.3 + "@types/keyv": ^4.2.0 + better-sqlite3: ^11.0.0 + cookie: ^0.6.0 + express: ^4.17.1 + fs-extra: ^11.0.0 + keyv: ^4.5.2 + knex: ^3.0.0 + msw: ^1.0.0 + mysql2: ^3.0.0 + pg: ^8.11.3 + pg-connection-string: ^2.3.0 + testcontainers: ^10.0.0 + textextensions: ^5.16.0 + uuid: ^9.0.0 + yn: ^4.0.0 + peerDependencies: + "@types/jest": "*" + checksum: 89b551093b4dc90b169646c0238b885bd31f4c2cafaf251aa6b4c2f6c4783e52b00068e43c5e6fcd0ac648a76b8502d43b0b133a1f36bbe7f4e3c1e1db17d417 + languageName: node + linkType: hard + "@backstage/catalog-client@npm:^1.6.5, @backstage/catalog-client@npm:^1.7.0": version: 1.7.0 resolution: "@backstage/catalog-client@npm:1.7.0" @@ -2913,7 +3237,7 @@ __metadata: languageName: node linkType: hard -"@backstage/config-loader@npm:^1.8.1, @backstage/config-loader@npm:^1.9.1": +"@backstage/config-loader@npm:^1.8.1, @backstage/config-loader@npm:^1.9.0, @backstage/config-loader@npm:^1.9.1": version: 1.9.1 resolution: "@backstage/config-loader@npm:1.9.1" dependencies: @@ -2997,7 +3321,7 @@ __metadata: languageName: node linkType: hard -"@backstage/integration@npm:^1.13.0, @backstage/integration@npm:^1.15.0": +"@backstage/integration@npm:^1.13.0, @backstage/integration@npm:^1.14.0, @backstage/integration@npm:^1.15.0": version: 1.15.0 resolution: "@backstage/integration@npm:1.15.0" dependencies: @@ -3042,7 +3366,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-auth-node@npm:^0.5.2": +"@backstage/plugin-auth-node@npm:^0.5.1, @backstage/plugin-auth-node@npm:^0.5.2": version: 0.5.2 resolution: "@backstage/plugin-auth-node@npm:0.5.2" dependencies: @@ -3137,6 +3461,15 @@ __metadata: languageName: node linkType: hard +"@backstage/plugin-events-node@npm:^0.3.10, @backstage/plugin-events-node@npm:^0.3.8": + version: 0.3.10 + resolution: "@backstage/plugin-events-node@npm:0.3.10" + dependencies: + "@backstage/backend-plugin-api": ^0.8.1 + checksum: 19a6cb5541e08e905d6015c92c3e3f3c8707b0ec31a9638832106b55b0e9999156ff7a4360c424f12b105f4d1b710fa14f1435fb58812a64601e4890d575287b + languageName: node + linkType: hard + "@backstage/plugin-events-node@npm:^0.4.0": version: 0.4.0 resolution: "@backstage/plugin-events-node@npm:0.4.0" @@ -3161,7 +3494,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-permission-node@npm:^0.8.3": +"@backstage/plugin-permission-node@npm:^0.8.0, @backstage/plugin-permission-node@npm:^0.8.2, @backstage/plugin-permission-node@npm:^0.8.3": version: 0.8.3 resolution: "@backstage/plugin-permission-node@npm:0.8.3" dependencies: @@ -4112,6 +4445,20 @@ __metadata: languageName: node linkType: hard +"@exodus/schemasafe@npm:^1.0.0-rc.2": + version: 1.3.0 + resolution: "@exodus/schemasafe@npm:1.3.0" + checksum: 5fa00ce28d142dc39e07d8080e7967e77125bfdf59af31975b7e6395ca5265e2a8540ab7d8cc89abf8c0a483560f8dbb2994761115c995d2c473ab4b6ec74dba + languageName: node + linkType: hard + +"@fastify/busboy@npm:^2.0.0": + version: 2.1.1 + resolution: "@fastify/busboy@npm:2.1.1" + checksum: 42c32ef75e906c9a4809c1e1930a5ca6d4ddc8d138e1a8c8ba5ea07f997db32210617d23b2e4a85fe376316a41a1a0439fc6ff2dedf5126d96f45a9d80754fb2 + languageName: node + linkType: hard + "@gar/promisify@npm:^1.1.3": version: 1.1.3 resolution: "@gar/promisify@npm:1.1.3" @@ -4700,6 +5047,15 @@ __metadata: languageName: node linkType: hard +"@keyv/serialize@npm:*": + version: 1.0.1 + resolution: "@keyv/serialize@npm:1.0.1" + dependencies: + buffer: ^6.0.3 + checksum: ff3dd9a6246b17fca3d1b0aba312dea931059fdecc36027f4d8133e59dbb3554a0a516b1f3dfc7fb2b3ca7a3d6fa307804f299566ab214febd3fb9d0502eebed + languageName: node + linkType: hard + "@kubernetes/client-node@npm:0.20.0": version: 0.20.0 resolution: "@kubernetes/client-node@npm:0.20.0" @@ -7515,7 +7871,7 @@ __metadata: languageName: node linkType: hard -"@types/dockerode@npm:^3.3.0": +"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.29": version: 3.3.31 resolution: "@types/dockerode@npm:3.3.31" dependencies: @@ -7711,6 +8067,15 @@ __metadata: languageName: node linkType: hard +"@types/keyv@npm:^4.2.0": + version: 4.2.0 + resolution: "@types/keyv@npm:4.2.0" + dependencies: + keyv: "*" + checksum: 8713da9382b9346d664866a6cab2f91b0fd479f61379af891303a618e9a2abad6f347adc38a0850540e3f2dad278427de24e7555339264fddb04d1d17d3b50e0 + languageName: node + linkType: hard + "@types/lodash@npm:^4.14.175": version: 4.17.7 resolution: "@types/lodash@npm:4.17.7" @@ -7937,6 +8302,15 @@ __metadata: languageName: node linkType: hard +"@types/ssh2-streams@npm:*": + version: 0.1.12 + resolution: "@types/ssh2-streams@npm:0.1.12" + dependencies: + "@types/node": "*" + checksum: aa0aa45e40cfca34b4443dafa8d28ff49196c05c71867cbf0a8cdd5127be4d8a3840819543fcad16535653ca8b0e29217671ed6500ff1e7a3ad2442c5d1b40a6 + languageName: node + linkType: hard + "@types/ssh2@npm:*": version: 1.15.1 resolution: "@types/ssh2@npm:1.15.1" @@ -7946,6 +8320,16 @@ __metadata: languageName: node linkType: hard +"@types/ssh2@npm:^0.5.48": + version: 0.5.52 + resolution: "@types/ssh2@npm:0.5.52" + dependencies: + "@types/node": "*" + "@types/ssh2-streams": "*" + checksum: bc1c76ac727ad73ddd59ba849cf0ea3ed2e930439e7a363aff24f04f29b74f9b1976369b869dc9a018223c9fb8ad041c09a0f07aea8cf46a8c920049188cddae + languageName: node + linkType: hard + "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -7975,6 +8359,16 @@ __metadata: languageName: node linkType: hard +"@types/swagger2openapi@npm:^7.0.4": + version: 7.0.4 + resolution: "@types/swagger2openapi@npm:7.0.4" + dependencies: + "@types/node": "*" + openapi-types: ^12.1.0 + checksum: e024802da721ee51ad5653802418f4c1b540739699ef8e35bd4983d08abc88d7e40e1cbd579e90efbbc4ca41fcd5edc7f08250f8a80abf09ab05db3922701c45 + languageName: node + linkType: hard + "@types/tough-cookie@npm:*": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" @@ -8865,7 +9259,7 @@ __metadata: languageName: node linkType: hard -"archiver@npm:^7.0.0": +"archiver@npm:^7.0.0, archiver@npm:^7.0.1": version: 7.0.1 resolution: "archiver@npm:7.0.1" dependencies: @@ -9154,7 +9548,7 @@ __metadata: languageName: node linkType: hard -"atlassian-openapi@npm:^1.0.8": +"atlassian-openapi@npm:^1.0.19, atlassian-openapi@npm:^1.0.8": version: 1.0.19 resolution: "atlassian-openapi@npm:1.0.19" dependencies: @@ -9232,10 +9626,10 @@ __metadata: languageName: node linkType: hard -"b4a@npm:^1.6.4": - version: 1.6.6 - resolution: "b4a@npm:1.6.6" - checksum: c46a27e3ac9c84426ae728f0fc46a6ae7703a7bc03e771fa0bef4827fd7cf3bb976d1a3d5afff54606248372ab8fdf595bd0114406690edf37f14d120630cf7f +"b4a@npm:^1.6.4, b4a@npm:^1.6.6": + version: 1.6.7 + resolution: "b4a@npm:1.6.7" + checksum: afe4e239b49c0ef62236fe0d788ac9bd9d7eac7e9855b0d1835593cd0efcc7be394f9cc28a747a2ed2cdcb0a48c3528a551a196f472eb625457c711169c9efa2 languageName: node linkType: hard @@ -9361,10 +9755,47 @@ __metadata: languageName: node linkType: hard -"bare-events@npm:^2.2.0": - version: 2.4.2 - resolution: "bare-events@npm:2.4.2" - checksum: 6cd2b10dd32a3410787e120c091b6082fbc2df0c45ed723a7ae51d0e2f55d2a4037e1daff21dae90b671d36582f9f8d50df337875c281d10adb60df81b8cd861 +"bare-events@npm:^2.0.0, bare-events@npm:^2.2.0": + version: 2.5.0 + resolution: "bare-events@npm:2.5.0" + checksum: 5aa10716e7f33c5dfc471fd657eee2a33f2db0f78b3c83b5cdd1a45a7e7871114a69460ea96cd838807c55eb470b9e53dd0dfda8c83cced1352cc8253cebff48 + languageName: node + linkType: hard + +"bare-fs@npm:^2.1.1": + version: 2.3.5 + resolution: "bare-fs@npm:2.3.5" + dependencies: + bare-events: ^2.0.0 + bare-path: ^2.0.0 + bare-stream: ^2.0.0 + checksum: 071b1dff94a213eaf0b41693953959bf10af2deade597a56ff206a5d833579d56bc8530aa4614bb88bf39fd6d52f2404f7c36af4695109ffa756a13837ac3d91 + languageName: node + linkType: hard + +"bare-os@npm:^2.1.0": + version: 2.4.4 + resolution: "bare-os@npm:2.4.4" + checksum: e90088a7dc0307c020350a28df8ec5564cae5a4b7a213d8509d70831d7064308e2ed31de801b68f474cb004ad3a0a66bd28c38374d270484d9025ee71af20396 + languageName: node + linkType: hard + +"bare-path@npm:^2.0.0, bare-path@npm:^2.1.0": + version: 2.1.3 + resolution: "bare-path@npm:2.1.3" + dependencies: + bare-os: ^2.1.0 + checksum: 20301aeb05b735852a396515464908e51e896922c3bb353ef2a09ff54e81ced94e6ad857bb0a36d2ce659c42bd43dd5c3d5643edd8faaf910ee9950c4e137b88 + languageName: node + linkType: hard + +"bare-stream@npm:^2.0.0": + version: 2.3.0 + resolution: "bare-stream@npm:2.3.0" + dependencies: + b4a: ^1.6.6 + streamx: ^2.20.0 + checksum: 17de9dbd5a6d70863b6e55f0acdfe1cb5d2b05f22d87e79986372cc796095eb4882a868ee6ba3dc543243085d27f618b4b81ef2bf384bc1c690dd3a557b6e30d languageName: node linkType: hard @@ -11210,7 +11641,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -11570,6 +12001,27 @@ __metadata: languageName: node linkType: hard +"docker-compose@npm:^0.24.8": + version: 0.24.8 + resolution: "docker-compose@npm:0.24.8" + dependencies: + yaml: ^2.2.2 + checksum: 48f3564c46490f1f51899a144deb546b61450a76bffddb378379ac7702aa34b055e0237e0dc77507df94d7ad6f1f7daeeac27730230bce9aafe2e35efeda6b45 + languageName: node + linkType: hard + +"docker-modem@npm:^3.0.0": + version: 3.0.8 + resolution: "docker-modem@npm:3.0.8" + dependencies: + debug: ^4.1.1 + readable-stream: ^3.5.0 + split-ca: ^1.0.1 + ssh2: ^1.11.0 + checksum: e3675c9b1ad800be8fb1cb9c5621fbef20a75bfedcd6e01b69808eadd7f0165681e4e30d1700897b788a67dbf4769964fcccd19c3d66f6d2499bb7aede6b34df + languageName: node + linkType: hard + "docker-modem@npm:^5.0.3": version: 5.0.3 resolution: "docker-modem@npm:5.0.3" @@ -11582,6 +12034,17 @@ __metadata: languageName: node linkType: hard +"dockerode@npm:^3.3.5": + version: 3.3.5 + resolution: "dockerode@npm:3.3.5" + dependencies: + "@balena/dockerignore": ^1.0.2 + docker-modem: ^3.0.0 + tar-fs: ~2.0.1 + checksum: 7f6650422b07fa7ea9d5801f04b1a432634446b5fe37b995b8302b953b64e93abf1bb4596c2fb574ba47aafee685ef2ab959cc86c9654add5a26d09541bbbcc6 + languageName: node + linkType: hard + "dockerode@npm:^4.0.0": version: 4.0.2 resolution: "dockerode@npm:4.0.2" @@ -12126,6 +12589,13 @@ __metadata: languageName: node linkType: hard +"es6-promise@npm:^3.2.1": + version: 3.3.1 + resolution: "es6-promise@npm:3.3.1" + checksum: ce4044009c2b78db18b15212338eb711cd8a4d485961bc9ec18bb24e8c1e91c96d3295b0fcf63066fc0fa1b0ade36da05e6657827d4336dece382be2429b8398 + languageName: node + linkType: hard + "esbuild-loader@npm:^2.18.0": version: 2.21.0 resolution: "esbuild-loader@npm:2.21.0" @@ -13140,7 +13610,7 @@ __metadata: languageName: node linkType: hard -"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.1.1": +"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.0.7, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d @@ -13759,6 +14229,13 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^5.1.1": + version: 5.1.1 + resolution: "get-port@npm:5.1.1" + checksum: 0162663ffe5c09e748cd79d97b74cd70e5a5c84b760a475ce5767b357fb2a57cb821cee412d646aa8a156ed39b78aab88974eddaa9e5ee926173c036c0713787 + languageName: node + linkType: hard + "get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -14498,6 +14975,13 @@ __metadata: languageName: node linkType: hard +"http2-client@npm:^1.2.5": + version: 1.3.5 + resolution: "http2-client@npm:1.3.5" + checksum: 075970adefeb86538fbef810d46586569a689711d4585f4969e0f167344a8e59857eddc0203b428aea83a278070ffd1b3a3192529e93c47a88c71da9295258eb + languageName: node + linkType: hard + "https-browserify@npm:^1.0.0": version: 1.0.0 resolution: "https-browserify@npm:1.0.0" @@ -16422,6 +16906,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:*": + version: 5.0.3 + resolution: "keyv@npm:5.0.3" + dependencies: + "@keyv/serialize": "*" + checksum: bb2cbce2bc3fe6723bc17443e0f6fd9b9a590bbb245b0e8fb030a8c20c6ab28c11e2ee4f727778a259449c18f5f4c76bc5b20dc80ea67d1c84018fc251fb8d89 + languageName: node + linkType: hard + "keyv@npm:^4.5.2, keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -17656,6 +18149,40 @@ __metadata: languageName: node linkType: hard +"msw@npm:^1.0.0": + version: 1.3.4 + resolution: "msw@npm:1.3.4" + dependencies: + "@mswjs/cookies": ^0.2.2 + "@mswjs/interceptors": ^0.17.10 + "@open-draft/until": ^1.0.3 + "@types/cookie": ^0.4.1 + "@types/js-levenshtein": ^1.1.1 + chalk: ^4.1.1 + chokidar: ^3.4.2 + cookie: ^0.4.2 + graphql: ^16.8.1 + headers-polyfill: 3.2.5 + inquirer: ^8.2.0 + is-node-process: ^1.2.0 + js-levenshtein: ^1.1.6 + node-fetch: ^2.6.7 + outvariant: ^1.4.0 + path-to-regexp: ^6.2.0 + strict-event-emitter: ^0.4.3 + type-fest: ^2.19.0 + yargs: ^17.3.1 + peerDependencies: + typescript: ">= 4.4.x" + peerDependenciesMeta: + typescript: + optional: true + bin: + msw: cli/index.js + checksum: 57646ecb831e98f00387e60bad4d535e426d406ae2645340e59500c219059be225f1f02a5ff21aee9daeb7a8bdde922a00fb82930781d27e3f3fdaf6b292c25f + languageName: node + linkType: hard + "multer@npm:^1.4.5-lts.1": version: 1.4.5-lts.1 resolution: "multer@npm:1.4.5-lts.1" @@ -17840,6 +18367,15 @@ __metadata: languageName: node linkType: hard +"node-fetch-h2@npm:^2.3.0": + version: 2.3.0 + resolution: "node-fetch-h2@npm:2.3.0" + dependencies: + http2-client: ^1.2.5 + checksum: c836d6dc019ba399514e14502e6a36e16838cd5633c1525dff915f9d4ee86b7a73ef30d72327451699bd2eabe59cbf362b2185d11eff10026cd9094655ef7ed8 + languageName: node + linkType: hard + "node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7, node-fetch@npm:^2.6.9, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" @@ -17947,6 +18483,15 @@ __metadata: languageName: node linkType: hard +"node-readfiles@npm:^0.2.0": + version: 0.2.0 + resolution: "node-readfiles@npm:0.2.0" + dependencies: + es6-promise: ^3.2.1 + checksum: 1993bf6bff633e321ea7a8a9cd4b5d5338c2ce28b0d8fa585b68b482086b6e27e0421ff7fa4d3269bbbe2058ac230777ab708886adc48621169f699f5276772a + languageName: node + linkType: hard + "node-releases@npm:^2.0.18": version: 2.0.18 resolution: "node-releases@npm:2.0.18" @@ -18078,6 +18623,64 @@ __metadata: languageName: node linkType: hard +"oas-kit-common@npm:^1.0.8": + version: 1.0.8 + resolution: "oas-kit-common@npm:1.0.8" + dependencies: + fast-safe-stringify: ^2.0.7 + checksum: 576ab5f7c7fde551a9c780fde9392cb9dec5159b62c3ad4499e334bffdb12e089e97dccf2a9d0d1ac5be208f9d6f0e72da5ac3744d878134ef0177eed135cc52 + languageName: node + linkType: hard + +"oas-linter@npm:^3.2.2": + version: 3.2.2 + resolution: "oas-linter@npm:3.2.2" + dependencies: + "@exodus/schemasafe": ^1.0.0-rc.2 + should: ^13.2.1 + yaml: ^1.10.0 + checksum: 24affafc6ac424a0771e5d8ebabc37bde6b020641407b785e82d01254289b34467b76e77be1dedc949e3af5a3b00f419c0395abdb7f469b3008b2686febf6960 + languageName: node + linkType: hard + +"oas-resolver@npm:^2.5.6": + version: 2.5.6 + resolution: "oas-resolver@npm:2.5.6" + dependencies: + node-fetch-h2: ^2.3.0 + oas-kit-common: ^1.0.8 + reftools: ^1.1.9 + yaml: ^1.10.0 + yargs: ^17.0.1 + bin: + resolve: resolve.js + checksum: 0654e30c0898fd2d160b5911528df302c517cbf9ffeada2e086f28d3b780df0b96aae51527b83085f292009e64154b52e5f71f221c2e7c4f354aa201bd710a48 + languageName: node + linkType: hard + +"oas-schema-walker@npm:^1.1.5": + version: 1.1.5 + resolution: "oas-schema-walker@npm:1.1.5" + checksum: 27bdeda1ebcf557b90cfb5d2ac3ca8e851f601d96215747c19ce0ae8f8458ad8012701b615fe313eacf4665b733f46ec12870f72d453251217b8a3ceb2be9abf + languageName: node + linkType: hard + +"oas-validator@npm:^5.0.8": + version: 5.0.8 + resolution: "oas-validator@npm:5.0.8" + dependencies: + call-me-maybe: ^1.0.1 + oas-kit-common: ^1.0.8 + oas-linter: ^3.2.2 + oas-resolver: ^2.5.6 + oas-schema-walker: ^1.1.5 + reftools: ^1.1.9 + should: ^13.2.1 + yaml: ^1.10.0 + checksum: 9f4c27022f961ca60472a9094803f347e986f7fb9f0dd12e14bafe5dcfdb677f7108a90dc01934af167e7cf27d78ac06b9cb63ecdd9cb5d8f532a0cf83242df9 + languageName: node + linkType: hard + "oauth-sign@npm:~0.9.0": version: 0.9.0 resolution: "oauth-sign@npm:0.9.0" @@ -18285,7 +18888,7 @@ __metadata: languageName: node linkType: hard -"openapi-merge@npm:^1.3.2": +"openapi-merge@npm:^1.3.2, openapi-merge@npm:^1.3.3": version: 1.3.3 resolution: "openapi-merge@npm:1.3.3" dependencies: @@ -18296,7 +18899,7 @@ __metadata: languageName: node linkType: hard -"openapi-types@npm:^12.0.2": +"openapi-types@npm:^12.0.2, openapi-types@npm:^12.1.0": version: 12.1.3 resolution: "openapi-types@npm:12.1.3" checksum: 7fa5547f87a58d2aa0eba6e91d396f42d7d31bc3ae140e61b5d60b47d2fd068b48776f42407d5a8da7280cf31195aa128c2fc285e8bb871d1105edee5647a0bb @@ -19618,6 +20221,26 @@ __metadata: languageName: node linkType: hard +"proper-lockfile@npm:^4.1.2": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: ^4.2.4 + retry: ^0.12.0 + signal-exit: ^3.0.2 + checksum: 00078ee6a61c216a56a6140c7d2a98c6c733b3678503002dc073ab8beca5d50ca271de4c85fca13b9b8ee2ff546c36674d1850509b84a04a5d0363bcb8638939 + languageName: node + linkType: hard + +"properties-reader@npm:^2.3.0": + version: 2.3.0 + resolution: "properties-reader@npm:2.3.0" + dependencies: + mkdirp: ^1.0.4 + checksum: cbf59e862dc507f8ce1f8d7641ed9737119f16a1d4dad8e79f17b303aaca1c6af7d36ddfef0f649cab4d200ba4334ac159af0b238f6978a085f5b1b5126b6cc3 + languageName: node + linkType: hard + "property-expr@npm:^2.0.4": version: 2.0.6 resolution: "property-expr@npm:2.0.6" @@ -20017,6 +20640,13 @@ __metadata: languageName: node linkType: hard +"reftools@npm:^1.1.9": + version: 1.1.9 + resolution: "reftools@npm:1.1.9" + checksum: 3b096e6a75ca3003f0642f69784d4eaad0caad75f0ae3b99f04f6b49380f34dbdeb21d8bf97f184d6ca5aef570bbeb1ef10ee2144494a50fc056c6b2e1422043 + languageName: node + linkType: hard + "regenerate-unicode-properties@npm:^10.1.0": version: 10.1.1 resolution: "regenerate-unicode-properties@npm:10.1.1" @@ -20964,6 +21594,62 @@ __metadata: languageName: node linkType: hard +"should-equal@npm:^2.0.0": + version: 2.0.0 + resolution: "should-equal@npm:2.0.0" + dependencies: + should-type: ^1.4.0 + checksum: 3f3580a223bf76f9309a4d957d2dcbd6059bda816f2e6656e822b7518218ef653c25e9271b2f5765ca6f5a72a217105ad343a8ceea831d15aff44dd691cc1dcd + languageName: node + linkType: hard + +"should-format@npm:^3.0.3": + version: 3.0.3 + resolution: "should-format@npm:3.0.3" + dependencies: + should-type: ^1.3.0 + should-type-adaptors: ^1.0.1 + checksum: 5304e89b4d4c42078c7f66232d13cca1d6a1c00c173f500f64160f57d4ecd7522a25106b313fe8f8694547e8a1ce4d975f1f09a3d1618f1dc054db48c0683d87 + languageName: node + linkType: hard + +"should-type-adaptors@npm:^1.0.1": + version: 1.1.0 + resolution: "should-type-adaptors@npm:1.1.0" + dependencies: + should-type: ^1.3.0 + should-util: ^1.0.0 + checksum: 94dd1d225c8f2590278f46689258a1df684ca1f26262459c4e2d64a09d06935ec1410a24fe7b5f98b9429093e48afef2ed1b370634e0444b930547df4943f70d + languageName: node + linkType: hard + +"should-type@npm:^1.3.0, should-type@npm:^1.4.0": + version: 1.4.0 + resolution: "should-type@npm:1.4.0" + checksum: 88d9324c6c0c2f94e71d2f8b11c84e44de81f16eeb6fafcba47f4af430c65e46bad18eb472827526cad22b4fe693aba8b022739d1c453672faf28860df223491 + languageName: node + linkType: hard + +"should-util@npm:^1.0.0": + version: 1.0.1 + resolution: "should-util@npm:1.0.1" + checksum: c3be15e0fdc851f8338676b3f8b590d330bbea94ec41c1343cc9983dea295915073f69a215795454b6adda6579ec8927c7c0ab178b83f9f11a0247ccdba53381 + languageName: node + linkType: hard + +"should@npm:^13.2.1": + version: 13.2.3 + resolution: "should@npm:13.2.3" + dependencies: + should-equal: ^2.0.0 + should-format: ^3.0.3 + should-type: ^1.4.0 + should-type-adaptors: ^1.0.1 + should-util: ^1.0.0 + checksum: 74bcc0eb85e0a63a88e501ff9ca3b53dbc6d1ee47823c029a18a4b14b3ef4e2561733e161033df720599d2153283470e9647fdcb1bbc78903960ffb0363239c4 + languageName: node + linkType: hard + "side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": version: 1.0.6 resolution: "side-channel@npm:1.0.6" @@ -21257,7 +21943,17 @@ __metadata: languageName: node linkType: hard -"ssh2@npm:^1.15.0": +"ssh-remote-port-forward@npm:^1.0.4": + version: 1.0.4 + resolution: "ssh-remote-port-forward@npm:1.0.4" + dependencies: + "@types/ssh2": ^0.5.48 + ssh2: ^1.4.0 + checksum: c6c04c5ddfde7cb06e9a8655a152bd28fe6771c6fe62ff0bc08be229491546c410f30b153c968b8d6817a57d38678a270c228f30143ec0fe1be546efc4f6b65a + languageName: node + linkType: hard + +"ssh2@npm:^1.11.0, ssh2@npm:^1.15.0, ssh2@npm:^1.4.0": version: 1.16.0 resolution: "ssh2@npm:1.16.0" dependencies: @@ -21453,7 +22149,7 @@ __metadata: languageName: node linkType: hard -"streamx@npm:^2.15.0": +"streamx@npm:^2.15.0, streamx@npm:^2.20.0": version: 2.20.1 resolution: "streamx@npm:2.20.1" dependencies: @@ -21860,6 +22556,38 @@ __metadata: languageName: node linkType: hard +"swagger-converter@npm:2.1.0": + version: 2.1.0 + resolution: "swagger-converter@npm:2.1.0" + dependencies: + urijs: ^1.19.6 + checksum: abc192050e0e0741fe82b8426db04f92a23e8478f3f4d9b67931ecc35482243791c44d1c1a7ed73cbfb9d5d809a706a67812569229c3b9b928a17f7dd3e7ea8c + languageName: node + linkType: hard + +"swagger2openapi@npm:^7.0.4": + version: 7.0.8 + resolution: "swagger2openapi@npm:7.0.8" + dependencies: + call-me-maybe: ^1.0.1 + node-fetch: ^2.6.1 + node-fetch-h2: ^2.3.0 + node-readfiles: ^0.2.0 + oas-kit-common: ^1.0.8 + oas-resolver: ^2.5.6 + oas-schema-walker: ^1.1.5 + oas-validator: ^5.0.8 + reftools: ^1.1.9 + yaml: ^1.10.0 + yargs: ^17.0.1 + bin: + boast: boast.js + oas-validate: oas-validate.js + swagger2openapi: swagger2openapi.js + checksum: dd0ee3b9dc3517639215471ec5bb013fcf2aa65dbee9089ec2ec4d911981ae381c63261a0a2f4e90cda668da27db6d2f5fdb89b0775cd1463a3a7f98d319c7ac + languageName: node + linkType: hard + "swc-loader@npm:^0.2.3": version: 0.2.6 resolution: "swc-loader@npm:0.2.6" @@ -21905,6 +22633,23 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^3.0.6": + version: 3.0.6 + resolution: "tar-fs@npm:3.0.6" + dependencies: + bare-fs: ^2.1.1 + bare-path: ^2.1.0 + pump: ^3.0.0 + tar-stream: ^3.1.5 + dependenciesMeta: + bare-fs: + optional: true + bare-path: + optional: true + checksum: b4fa09c70f75caf05bf5cf87369cd2862f1ac5fb75c4ddf9d25d55999f7736a94b58ad679d384196cba837c5f5ff14086e060fafccef5474a16e2d3058ffa488 + languageName: node + linkType: hard + "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -21930,7 +22675,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^3.0.0": +"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5": version: 3.1.7 resolution: "tar-stream@npm:3.1.7" dependencies: @@ -22038,6 +22783,29 @@ __metadata: languageName: node linkType: hard +"testcontainers@npm:^10.0.0": + version: 10.13.1 + resolution: "testcontainers@npm:10.13.1" + dependencies: + "@balena/dockerignore": ^1.0.2 + "@types/dockerode": ^3.3.29 + archiver: ^7.0.1 + async-lock: ^1.4.1 + byline: ^5.0.0 + debug: ^4.3.5 + docker-compose: ^0.24.8 + dockerode: ^3.3.5 + get-port: ^5.1.1 + proper-lockfile: ^4.1.2 + properties-reader: ^2.3.0 + ssh-remote-port-forward: ^1.0.4 + tar-fs: ^3.0.6 + tmp: ^0.2.3 + undici: ^5.28.4 + checksum: 5e2526f25574957a417fd8702c0b155dfd87ec6e55733c81be507b46e76e6df5ebcbed28b70b2a71339efae2c875b8341423885cf76df18b6be83a098010e3f5 + languageName: node + linkType: hard + "text-decoder@npm:^1.1.0": version: 1.1.1 resolution: "text-decoder@npm:1.1.1" @@ -22061,6 +22829,13 @@ __metadata: languageName: node linkType: hard +"textextensions@npm:^5.16.0": + version: 5.16.0 + resolution: "textextensions@npm:5.16.0" + checksum: d2abd5c962760046aa85d9ca542bd8bdb451370fc0a5e5f807aa80dd2f50175ec10d5ce9d28ae96968aaf6a1b1bea254cf4715f24852d0dcf29c6a60af7f793c + languageName: node + linkType: hard + "thenify-all@npm:^1.0.0": version: 1.6.0 resolution: "thenify-all@npm:1.6.0" @@ -22136,6 +22911,13 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.3": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 73b5c96b6e52da7e104d9d44afb5d106bb1e16d9fa7d00dbeb9e6522e61b571fbdb165c756c62164be9a3bbe192b9b268c236d370a2a0955c7689cd2ae377b95 + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -22716,6 +23498,15 @@ __metadata: languageName: node linkType: hard +"undici@npm:^5.28.4": + version: 5.28.4 + resolution: "undici@npm:5.28.4" + dependencies: + "@fastify/busboy": ^2.0.0 + checksum: a8193132d84540e4dc1895ecc8dbaa176e8a49d26084d6fbe48a292e28397cd19ec5d13bc13e604484e76f94f6e334b2bdc740d5f06a6e50c44072818d0c19f9 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" @@ -22867,7 +23658,7 @@ __metadata: languageName: node linkType: hard -"urijs@npm:^1.19.10, urijs@npm:^1.19.11": +"urijs@npm:^1.19.10, urijs@npm:^1.19.11, urijs@npm:^1.19.6": version: 1.19.11 resolution: "urijs@npm:1.19.11" checksum: f9b95004560754d30fd7dbee44b47414d662dc9863f1cf5632a7c7983648df11d23c0be73b9b4f9554463b61d5b0a520b70df9e1ee963ebb4af02e6da2cc80f3 @@ -23705,7 +24496,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.2.1": +"yaml@npm:^2.0.0, yaml@npm:^2.0.0-10, yaml@npm:^2.2.1, yaml@npm:^2.2.2": version: 2.5.1 resolution: "yaml@npm:2.5.1" bin: @@ -23743,7 +24534,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.2": +"yargs@npm:^17.0.1, yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: