diff --git a/package.json b/package.json index fb3a777..998ea0e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@reapit-cdk/generate-readme": "workspace:^", "@reapit-cdk/integration-tests": "workspace:^", "@types/jest": "^29.5.5", + "@types/swagger-ui-dist": "^3.30.3", "aws-cdk": "2.100.0", "aws-sdk-client-mock": "^3.0.0", "aws-sdk-client-mock-jest": "^3.0.0", diff --git a/packages/constructs/cross-region-stack-export/readme.md b/packages/constructs/cross-region-stack-export/readme.md new file mode 100644 index 0000000..254b2d9 --- /dev/null +++ b/packages/constructs/cross-region-stack-export/readme.md @@ -0,0 +1,59 @@ +# @reapit-cdk/cross-region-stack-export + + +![npm version](https://img.shields.io/npm/v/@reapit-cdk/cross-region-stack-export) +![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/cross-region-stack-export) +![coverage: 74.02%25](https://img.shields.io/badge/coverage-74.02%25-orange) +![Integ Tests: X](https://img.shields.io/badge/Integ%20Tests-X-red) + +Allows you to share values between stack across regions and accounts. + +## Package Installation: + +```sh +yarn add --dev @reapit-cdk/cross-region-stack-export +# or +npm install @reapit-cdk/cross-region-stack-export --save-dev +``` + +## Usage +```ts +import { CfnOutput, Stack, App } from 'aws-cdk-lib' +import { CrossRegionStackExport } from '@reapit-cdk/cross-region-stack-export' +import { Bucket } from 'aws-cdk-lib/aws-s3' + +const app = new App() +const euStack = new Stack(app, 'stack-eu', { + env: { + account: '11111111', + region: 'eu-west-1', + }, +}) + +const exporter = new CrossRegionStackExport(euStack, 'exporter') +exporter.setValue('thing', 'avalue') + +const bucket = new Bucket(euStack, 'bucket') +exporter.setValue('bucketArn', bucket.bucketArn) + +const usStack = new Stack(app, 'stack-us', { + env: { + account: '2222222222', + region: 'us-east-1', + }, +}) + +const importer = exporter.getImporter(usStack, 'eu-importer') + +const euThing = importer.getValue('thing') +const euBucket = Bucket.fromBucketArn(usStack, 'eu-bucket', importer.getValue('bucketArn')) + +new CfnOutput(usStack, 'euThing', { + value: euThing, +}) + +new CfnOutput(usStack, 'euBucketName', { + value: euBucket.bucketName, +}) + +``` \ No newline at end of file diff --git a/packages/constructs/edge-api-swagger/.npmignore b/packages/constructs/edge-api-swagger/.npmignore new file mode 100644 index 0000000..7b21160 --- /dev/null +++ b/packages/constructs/edge-api-swagger/.npmignore @@ -0,0 +1,4 @@ +src +tests +.eslintrc.js +tsconfig.json diff --git a/packages/constructs/edge-api-swagger/package.json b/packages/constructs/edge-api-swagger/package.json new file mode 100644 index 0000000..743ec79 --- /dev/null +++ b/packages/constructs/edge-api-swagger/package.json @@ -0,0 +1,53 @@ +{ + "name": "@reapit-cdk/edge-api-swagger", + "version": "0.0.0", + "description": "Add a swagger endpoint to your EdgeAPI", + "homepage": "https://github.com/reapit/ts-cdk-constructs/blob/main/packages/constructs/edge-api-swagger", + "readme": "https://github.com/reapit/ts-cdk-constructs/blob/main/packages/constructs/edge-api-swagger/readme.md", + "bugs": { + "url": "https://github.com/reapit/ts-cdk-constructs/issues" + }, + "license": "MIT", + "author": { + "name": "Josh Balfour", + "email": "jbalfour@reapit.com" + }, + "repository": { + "url": "https://github.com/reapit/ts-cdk-constructs.git" + }, + "scripts": { + "build": "reapit-cdk-tsup", + "check": "yarn run root:check -p $(pwd)", + "lint": "reapit-cdk-eslint", + "test": "yarn run root:test -- $(pwd)", + "prepack": "reapit-version-package && yarn build", + "integ": "yarn run root:integ -- $(pwd)", + "jsii:build": "rpt-cdk-jsii", + "jsii:publish": "rpt-cdk-jsii --publish" + }, + "main": "src/index.ts", + "types": "src/index.ts", + "publishConfig": { + "main": "dist/index.js", + "types": "dist/index.d.ts" + }, + "dependencies": { + "openapi3-ts": "^4.1.2", + "swagger-ui-dist": "^5.9.0", + "typescript": "^5.1.3" + }, + "peerDependencies": { + "@reapit-cdk/edge-api": "workspace:^", + "aws-cdk-lib": "^2.96.2", + "constructs": "^10.2.70" + }, + "devDependencies": { + "@reapit-cdk/eslint-config": "workspace:^", + "@reapit-cdk/integration-tests": "workspace:^", + "@reapit-cdk/jsii": "workspace:^", + "@reapit-cdk/tsup": "workspace:^", + "@reapit-cdk/version-package": "workspace:^", + "aws-cdk-lib": "^2.96.2", + "constructs": "^10.2.70" + } +} diff --git a/packages/constructs/edge-api-swagger/readme.md b/packages/constructs/edge-api-swagger/readme.md new file mode 100644 index 0000000..c19cca9 --- /dev/null +++ b/packages/constructs/edge-api-swagger/readme.md @@ -0,0 +1,73 @@ +# @reapit-cdk/edge-api-swagger + + +![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api-swagger) +![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api-swagger) +![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) +![Integ Tests: X](https://img.shields.io/badge/Integ%20Tests-X-red) + +Add a swagger endpoint to your EdgeAPI + +## Package Installation: + +```sh +yarn add --dev @reapit-cdk/edge-api-swagger +# or +npm install @reapit-cdk/edge-api-swagger --save-dev +``` + +## Usage +```ts +import { Stack, App } from 'aws-cdk-lib' +import { EdgeAPI, EdgeAPILambda } from '@reapit-cdk/edge-api' +import { Code, Runtime } from 'aws-cdk-lib/aws-lambda' +import { EdgeAPISwaggerEndpoint } from '@reapit-cdk/edge-api-swagger' +import { Certificate } from 'aws-cdk-lib/aws-certificatemanager' +import * as path from 'path' + +const app = new App() +const stack = new Stack(app, 'stack-name') + +const certificate = new Certificate(stack, 'certificate', { + domainName: 'example.org', +}) +const api = new EdgeAPI(stack, 'api', { + certificate, + domains: ['example.org', 'example.com'], + devMode: false, + defaultEndpoint: { + destination: 'example.com', + }, +}) + +const lambda = new EdgeAPILambda(stack, 'lambda', { + code: Code.fromAsset(path.resolve('../lambda/dist')), + codePath: path.resolve('../lambda/src/index.ts'), // gets added to the docs + handler: 'index.handler', + runtime: Runtime.NODEJS_18_X, + environment: { + aVariable: 'contents', + }, +}) + +api.addEndpoint({ + pathPattern: '/api/lambda', + lambda, +}) + +api.addEndpoint( + new EdgeAPISwaggerEndpoint(stack, 'docs', { + api, + url: 'https://example.org', + + pathPattern: '/swagger', // optional, defaults to /swagger + + // optional + info: { + title: '', // defaults to Edge API + version: '', // defaults to 1.0.0 + }, + }), +) + +``` \ No newline at end of file diff --git a/packages/constructs/edge-api-swagger/src/edge-api-swagger-endpoint.ts b/packages/constructs/edge-api-swagger/src/edge-api-swagger-endpoint.ts new file mode 100644 index 0000000..3374b94 --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/edge-api-swagger-endpoint.ts @@ -0,0 +1,114 @@ +import { EdgeAPI, FrontendEndpoint, endpointIsLambdaEndpoint, endpointIsProxyEndpoint } from '@reapit-cdk/edge-api' +import { Construct } from 'constructs' + +import { Bucket } from 'aws-cdk-lib/aws-s3' +import { BucketDeployment, Source } from 'aws-cdk-lib/aws-s3-deployment' +import { RemovalPolicy } from 'aws-cdk-lib' +import { generateOpenAPIDocs } from './generation/generate-openapi-docs' +import { EndpointInputItem } from './generation' +import { getAbsoluteFSPath } from 'swagger-ui-dist' +import { InfoObject } from 'openapi3-ts/oas30' + +interface EdgeAPISwaggerEndpointProps { + api: EdgeAPI + url: string + pathPattern?: string + info?: InfoObject +} + +const swaggerHtml = (urlPrefix: string) => ` + + + + + + SwaggerUI + + + +
+ + + + +` + +export class EdgeAPISwaggerEndpoint extends Construct implements FrontendEndpoint { + bucket: Bucket + invalidationItems: string[] + pathPattern: string + + constructor(scope: Construct, id: string, props: EdgeAPISwaggerEndpointProps) { + super(scope, id) + this.pathPattern = props.pathPattern ?? '/swagger' + + const destinationKeyPrefix = this.pathPattern.replace(/^\/+/g, '') + + this.bucket = new Bucket(this, 'bucket', { + websiteIndexDocument: 'index.html', + websiteErrorDocument: destinationKeyPrefix + '/index.html', + removalPolicy: RemovalPolicy.RETAIN, // otherwise deletion will fail on stack destroy due to non-empty bucket + publicReadAccess: true, + blockPublicAccess: { + blockPublicAcls: false, + blockPublicPolicy: false, + ignorePublicAcls: false, + restrictPublicBuckets: false, + }, + }) + + const openapiJson = generateOpenAPIDocs({ + url: props.url, + info: props.info, + endpointsInput: props.api._endpoints.map((endpoint): EndpointInputItem => { + if (endpointIsLambdaEndpoint(endpoint)) { + return { + codePath: endpoint.lambda.codePath, + pathPattern: endpoint.pathPattern, + isFrontend: false, + } + } + if (endpointIsProxyEndpoint(endpoint)) { + return { + isProxy: true, + pathPattern: endpoint.pathPattern, + proxyDestination: endpoint.destination, + } + } + return { + pathPattern: endpoint.pathPattern, + isFrontend: true, + } + }), + }) + + new BucketDeployment(this, 'deployment', { + sources: [ + Source.data('index.html', swaggerHtml(`${props.url}/${destinationKeyPrefix}`)), + Source.jsonData('openapi.json', openapiJson), + Source.asset(getAbsoluteFSPath()), + ], + destinationBucket: this.bucket, + destinationKeyPrefix, + retainOnDelete: false, + }) + + this.invalidationItems = [ + '/index.html', + '/openapi.json', + '/swagger-ui-bundle.js', + '/swagger-ui-standalone-preset.js', + '/swagger-ui.css', + ] + } +} diff --git a/packages/constructs/edge-api-swagger/src/generation/endpoint-handlers-to-openapi.ts b/packages/constructs/edge-api-swagger/src/generation/endpoint-handlers-to-openapi.ts new file mode 100644 index 0000000..91117ce --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/generation/endpoint-handlers-to-openapi.ts @@ -0,0 +1,195 @@ +import { + ContentObject, + InfoObject, + OpenApiBuilder, + PathItemObject, + RequestBodyObject, + ResponseObject, + SchemaObject, + SchemaObjectType, +} from 'openapi3-ts/oas30' +import { EndpointHandlerInfo, ResolvedProperty, ReturnType } from './types' +import { Destination } from '@reapit-cdk/edge-api' + +export const endpointHandlersToOpenApi = ( + endpointHandlersInfo: EndpointHandlerInfo[], + url: string, + info?: InfoObject, +) => { + const openApi = new OpenApiBuilder() + openApi.addInfo({ + title: 'Edge API', + version: '1.0.0', + ...(info ?? {}), + }) + openApi.addServer({ + url, + }) + + endpointHandlersInfo.map(transformEndpointHandler).forEach(({ path, pathItem }) => openApi.addPath(path, pathItem)) + + return openApi +} + +const stringIsSchemaObjectType = (str: string): str is SchemaObjectType => { + return ['integer', 'number', 'string', 'boolean', 'array'].includes(str) +} + +const propertyToSchema = (property: ResolvedProperty): SchemaObject => { + const type = property.typeName && stringIsSchemaObjectType(property.typeName) ? property.typeName : 'object' + const properties = property.properties?.reduce( + (pv, cv) => { + if (!cv.name) { + return pv + } + return { + ...pv, + [cv.name]: propertyToSchema(cv), + } + }, + {} as Record, + ) + + return { + type, + required: property.properties + ? (property.properties + .filter((rp) => !rp.isOptional) + .map((rp) => rp.name) + .filter(Boolean) as string[]) + : undefined, + properties, + } +} + +const transformBody = (body: ResolvedProperty, isForm?: boolean): RequestBodyObject => { + const contentType = isForm ? 'application/x-www-form-urlencoded' : 'application/json' + const content: ContentObject = { + [contentType]: { + schema: propertyToSchema(body), + }, + } + return { content } +} + +const returnTypeToResponse = (returnType?: ReturnType): ResponseObject => { + if (!returnType) { + return { + description: 'not typed', + } + } + + if (returnType.isRedirection) { + return { + description: 'redirection', + headers: { + location: { + schema: { + type: 'string', + example: 'https://google.com', + }, + }, + status: { + schema: { + type: 'number', + example: 302, + }, + }, + }, + } + } + + if (returnType.isJSONResponse && returnType.response) { + return { + description: 'default', + content: { + 'application/json': { + schema: propertyToSchema(returnType.response), + }, + }, + } + } + + return { + description: 'invalid return type', + } +} + +const destinationToDescription = (destination: Destination) => { + if (typeof destination === 'string') { + return `Proxy to ${destination}` + } + return `Domain mapped Proxy ${Object.entries(destination) + .map(([domain, mapping]) => { + if (typeof mapping === 'string') { + return `from ${domain} to ${mapping}` + } + return `from ${domain} to ${mapping.destination}${ + Object.keys(mapping).length > 1 ? ' with mapping object ' + JSON.stringify(mapping) : '' + }` + }) + .join(', ')}` +} + +const transformEndpointHandler = (endpointInfo: EndpointHandlerInfo): { path: string; pathItem: PathItemObject } => { + const { bodyType, returnType, isFormRequestHandler, description, isProxy, isFrontend, proxyDestination } = + endpointInfo + if (isProxy) { + if (!proxyDestination) { + throw new Error('proxy but no proxy destination for endpoint' + endpointInfo.pathPattern) + } + return { + path: endpointInfo.pathPattern, + pathItem: { + get: { + description: destinationToDescription(proxyDestination), + responses: { + default: { + description: 'proxy', + }, + }, + }, + }, + } + } + if (bodyType) { + return { + path: endpointInfo.pathPattern, + pathItem: { + post: { + description, + responses: { + default: returnTypeToResponse(returnType), + }, + requestBody: transformBody(bodyType, isFormRequestHandler), + }, + }, + } + } + + if (isFrontend) { + return { + path: endpointInfo.pathPattern, + pathItem: { + get: { + description: 'Serves bucket contents.', + responses: { + default: returnTypeToResponse(returnType), + }, + }, + }, + } + } + + return { + path: endpointInfo.pathPattern, + pathItem: { + get: { + description, + responses: { + default: returnTypeToResponse(returnType), + }, + }, + }, + } +} diff --git a/packages/constructs/edge-api-swagger/src/generation/generate-openapi-docs.ts b/packages/constructs/edge-api-swagger/src/generation/generate-openapi-docs.ts new file mode 100644 index 0000000..cdb01bc --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/generation/generate-openapi-docs.ts @@ -0,0 +1,15 @@ +import { InfoObject } from 'openapi3-ts/oas30' +import { getEndpointHandlerInfo, endpointHandlersToOpenApi, EndpointsInput } from '.' + +export const generateOpenAPIDocs = ({ + endpointsInput, + url, + info, +}: { + endpointsInput: EndpointsInput + url: string + info?: InfoObject +}) => { + const endpointHandlerInfo = getEndpointHandlerInfo(endpointsInput) + return endpointHandlersToOpenApi(endpointHandlerInfo, url, info).getSpec() +} diff --git a/packages/constructs/edge-api-swagger/src/generation/get-endpoint-handler-info.ts b/packages/constructs/edge-api-swagger/src/generation/get-endpoint-handler-info.ts new file mode 100644 index 0000000..fe8c810 --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/generation/get-endpoint-handler-info.ts @@ -0,0 +1,228 @@ +import * as ts from 'typescript' +import path from 'path' +import { ResolvedProperty, EndpointsInput, EndpointHandlerInfo } from './types' + +const typeSyntaxKindToString = (kind: ts.SyntaxKind) => { + switch (kind) { + case ts.SyntaxKind.StringKeyword: + return 'string' + case ts.SyntaxKind.NumberKeyword: + return 'number' + case ts.SyntaxKind.BooleanKeyword: + return 'boolean' + case ts.SyntaxKind.NullKeyword: + return 'null' + case ts.SyntaxKind.AnyKeyword: + return 'any' + } +} + +const cleanComment = (comment: string) => { + return comment + .split('\n') + .map((s) => { + if (s.startsWith('*')) { + return s.substring(1) + } + return s + }) + .join('\n') +} + +const resolveTypeNode = (typeNode: ts.TypeNode, typeChecker: ts.TypeChecker): ResolvedProperty => { + if (ts.isTypeLiteralNode(typeNode)) { + const properties: ResolvedProperty[] = forEachChildAsArray(typeNode) + .filter((node) => ts.isPropertySignature(node) && ts.isIdentifier(node.name) && node.type) + .map((node) => { + if (!(ts.isPropertySignature(node) && ts.isIdentifier(node.name) && node.type)) { + throw new Error('this should never happen') + } + if (ts.isTypeReferenceNode(node.type) || ts.isTypeLiteralNode(node.type)) { + return { + ...resolveTypeNode(node.type, typeChecker), + name: node.name.escapedText.toString(), + isOptional: !!node.questionToken, + } + } + return { + typeName: typeSyntaxKindToString(node.type.kind), + name: node.name.escapedText.toString(), + isOptional: !!node.questionToken, + } + }) + + return { + properties, + isLiteral: false, + } + } + + if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) { + const typeName = typeNode.typeName.escapedText.toString() + const type = typeChecker.getTypeAtLocation(typeNode) + const [declaration] = type.aliasSymbol?.declarations || [] + if (!declaration) { + throw new Error('unable to find declaration for type ' + type.aliasSymbol?.getName()) + } + if (!ts.isTypeAliasDeclaration(declaration)) { + throw new Error('unable to resolve type ' + typeName) + } + if (ts.isMappedTypeNode(declaration.type)) { + return { + typeName, + isMappedType: true, + isLiteral: false, + properties: typeNode.typeArguments?.map((ta) => resolveTypeArgs(ta, typeChecker)), + isOptional: !!declaration.type.questionToken, + } + } + const { properties, isOptional } = resolveTypeNode(declaration.type, typeChecker) + return { + typeName, + properties, + isLiteral: false, + isOptional, + } + } + + return { + typeName: typeSyntaxKindToString(typeNode.kind), + } +} + +const resolveTypeArgs = (tn: ts.TypeNode, typeChecker: ts.TypeChecker): ResolvedProperty => { + if (ts.isTypeReferenceNode(tn)) { + return resolveTypeNode(tn, typeChecker) + } + if (ts.isUnionTypeNode(tn)) { + return { + isLiteral: false, + isMappedType: false, + isUnionType: true, + properties: tn.types.map((t) => resolveTypeArgs(t, typeChecker)), + } + } + return { + typeName: typeSyntaxKindToString(tn.kind), + } +} + +const resolveReturnType = (callExpression: ts.CallExpression, typeChecker: ts.TypeChecker) => { + const [arrowFunc] = callExpression.arguments + if (!ts.isArrowFunction(arrowFunc) || !arrowFunc.type || !ts.isTypeReferenceNode(arrowFunc.type)) { + return + } + const [responseType] = arrowFunc.type.typeArguments || [] + if (!responseType || !ts.isTypeReferenceNode(responseType) || !ts.isIdentifier(responseType.typeName)) { + return + } + const returnTypeWrapper = responseType.typeName.escapedText.toString() + if (returnTypeWrapper === 'JSONResponse') { + const [typeArg] = responseType.typeArguments || [] + if (typeArg && ts.isTypeReferenceNode(typeArg)) { + return { + isJSONResponse: true, + response: resolveTypeArgs(typeArg, typeChecker), + } + } + } else if (returnTypeWrapper === 'RedirectionResponse') { + return { + isRedirection: true, + } + } +} + +const forEachChildAsArray = (node: ts.Node) => { + const children: ts.Node[] = [] + node.forEachChild((child) => { + children.push(child) + }) + return children +} + +const getHandler = (node: ts.Node) => { + const children = forEachChildAsArray(node) + const variableStatements = children.filter(ts.isVariableStatement) + + const exportedVariableStatements = variableStatements.filter( + (node) => !!node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword), + ) + + const handlerVariableStatement = exportedVariableStatements + .map((exportedVariableStatement) => + forEachChildAsArray(exportedVariableStatement) + .filter(ts.isVariableDeclarationList) + .map((variableDeclarationList) => variableDeclarationList.declarations), + ) + .flat(2) + .find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.escapedText === 'handler') + + return handlerVariableStatement +} + +const getTsStuff = (handlerFileLocation: string) => { + const file = path.resolve(handlerFileLocation) + const program = ts.createProgram([file], { allowJs: true }) + const sourceFile = program.getSourceFile(file) + const typeChecker = program.getTypeChecker()! + + if (!sourceFile) { + throw new Error('no source file') + } + + const handler = getHandler(sourceFile) + + if (!handler) { + throw new Error(`handler not found for file ${handlerFileLocation}`) + } + + const { initializer } = handler + + if (!initializer || !ts.isCallExpression(initializer) || !ts.isIdentifier(initializer.expression)) { + throw new Error(`invalid handler export found for file ${handlerFileLocation}`) + } + + if (!initializer.typeArguments) { + return + } + const isJsonRequestHandler = initializer.expression.escapedText === 'jsonRequestHandler' + const isFormRequestHandler = initializer.expression.escapedText === 'formRequestHandler' + + const comments: string[] = [`Found in ${handlerFileLocation}`] + ts.forEachLeadingCommentRange(sourceFile.text, handler.getFullStart(), (pos, end, kind) => { + const comment = + kind === ts.SyntaxKind.MultiLineCommentTrivia + ? sourceFile.text.slice(pos + 2, end - 2) + : sourceFile.text.slice(pos + 2, end) + comments.push(cleanComment(comment)) + }) + + const [env, body] = initializer.typeArguments + const envType = env ? resolveTypeNode(env, typeChecker) : undefined + const bodyType = body ? resolveTypeNode(body, typeChecker) : undefined + const returnType = resolveReturnType(initializer, typeChecker) + + return { + isJsonRequestHandler, + isFormRequestHandler, + envType, + bodyType, + returnType, + description: comments.join('\n'), + } +} + +export const getEndpointHandlerInfo = (endpoints: EndpointsInput): EndpointHandlerInfo[] => { + return endpoints.map(({ codePath, ...rest }) => { + if (!codePath) { + return rest + } + const handlerInfo = getTsStuff(codePath) + + return { + codePath, + ...rest, + ...handlerInfo, + } + }) +} diff --git a/packages/constructs/edge-api-swagger/src/generation/index.ts b/packages/constructs/edge-api-swagger/src/generation/index.ts new file mode 100644 index 0000000..5d3b9c9 --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/generation/index.ts @@ -0,0 +1,3 @@ +export * from './types' +export * from './get-endpoint-handler-info' +export * from './endpoint-handlers-to-openapi' diff --git a/packages/constructs/edge-api-swagger/src/generation/types.ts b/packages/constructs/edge-api-swagger/src/generation/types.ts new file mode 100644 index 0000000..9659b90 --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/generation/types.ts @@ -0,0 +1,41 @@ +import { Destination } from '@reapit-cdk/edge-api' + +export type EndpointHandlerInfo = { + pathPattern: string + codePath?: string + isJsonRequestHandler?: boolean + isFormRequestHandler?: boolean + envType?: ResolvedProperty + bodyType?: ResolvedProperty + returnType?: ReturnType + description?: string + isFrontend?: boolean + isProxy?: boolean + proxyDestination?: Destination +} + +export type EndpointInputItem = { + pathPattern: string + codePath?: string + isFrontend?: boolean + isProxy?: boolean + proxyDestination?: Destination +} + +export type EndpointsInput = EndpointInputItem[] + +export type ResolvedProperty = { + name?: string + typeName?: string + isLiteral?: boolean + properties?: ResolvedProperty[] + isMappedType?: boolean + isUnionType?: boolean + isOptional?: boolean +} + +export type ReturnType = { + response?: ResolvedProperty + isJSONResponse?: boolean + isRedirection?: boolean +} diff --git a/packages/constructs/edge-api-swagger/src/index.ts b/packages/constructs/edge-api-swagger/src/index.ts new file mode 100644 index 0000000..3761742 --- /dev/null +++ b/packages/constructs/edge-api-swagger/src/index.ts @@ -0,0 +1 @@ +export * from './edge-api-swagger-endpoint' diff --git a/packages/constructs/edge-api-swagger/tsconfig.json b/packages/constructs/edge-api-swagger/tsconfig.json new file mode 100644 index 0000000..5adfce4 --- /dev/null +++ b/packages/constructs/edge-api-swagger/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@reapit-cdk/tsconfig/base.json", +} \ No newline at end of file diff --git a/packages/constructs/edge-api-swagger/usage.ts b/packages/constructs/edge-api-swagger/usage.ts new file mode 100644 index 0000000..5fab54d --- /dev/null +++ b/packages/constructs/edge-api-swagger/usage.ts @@ -0,0 +1,51 @@ +import { Stack, App } from 'aws-cdk-lib' +import { EdgeAPI, EdgeAPILambda } from '@reapit-cdk/edge-api' +import { Code, Runtime } from 'aws-cdk-lib/aws-lambda' +import { EdgeAPISwaggerEndpoint } from '@reapit-cdk/edge-api-swagger' +import { Certificate } from 'aws-cdk-lib/aws-certificatemanager' +import * as path from 'path' + +const app = new App() +const stack = new Stack(app, 'stack-name') + +const certificate = new Certificate(stack, 'certificate', { + domainName: 'example.org', +}) +const api = new EdgeAPI(stack, 'api', { + certificate, + domains: ['example.org', 'example.com'], + devMode: false, + defaultEndpoint: { + destination: 'example.com', + }, +}) + +const lambda = new EdgeAPILambda(stack, 'lambda', { + code: Code.fromAsset(path.resolve('../lambda/dist')), + codePath: path.resolve('../lambda/src/index.ts'), // gets added to the docs + handler: 'index.handler', + runtime: Runtime.NODEJS_18_X, + environment: { + aVariable: 'contents', + }, +}) + +api.addEndpoint({ + pathPattern: '/api/lambda', + lambda, +}) + +api.addEndpoint( + new EdgeAPISwaggerEndpoint(stack, 'docs', { + api, + url: 'https://example.org', + + pathPattern: '/swagger', // optional, defaults to /swagger + + // optional + info: { + title: '', // defaults to Edge API + version: '', // defaults to 1.0.0 + }, + }), +) diff --git a/packages/constructs/edge-api/package.json b/packages/constructs/edge-api/package.json index 8f5621c..7f4499c 100644 --- a/packages/constructs/edge-api/package.json +++ b/packages/constructs/edge-api/package.json @@ -39,9 +39,6 @@ "constructs": "^10.2.70" }, "devDependencies": { - "@aws-sdk/client-dynamodb": "3.414.0", - "@aws-sdk/client-secrets-manager": "3.414.0", - "@aws-sdk/lib-dynamodb": "3.414.0", "@reapit-cdk/cloudfront-invalidation": "workspace:^", "@reapit-cdk/eslint-config": "workspace:^", "@reapit-cdk/integration-tests": "workspace:^", diff --git a/packages/constructs/edge-api/readme.md b/packages/constructs/edge-api/readme.md index 6343c6d..d932f24 100644 --- a/packages/constructs/edge-api/readme.md +++ b/packages/constructs/edge-api/readme.md @@ -3,7 +3,7 @@ ![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api) -![coverage: 97.85%25](https://img.shields.io/badge/coverage-97.85%25-green) +![coverage: 96.97%25](https://img.shields.io/badge/coverage-96.97%25-green) ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) This construct creates a truly globally available API where code executes at the edge. Because changes take a long time to propagate to all edge locations, there is a `devMode` flag which will instead deploy your API to a [HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html). This is compatible with hotswapping, so [`cdk watch`](https://docs.aws.amazon.com/cdk/v2/guide/cli.html#cli-deploy-watch) works very well. In order to make it easy to develop APIs which handle both event formats and work around the environment variable limitation, I recommend you use the lightweight request wrapper [@reapit-cdk/edge-api-sdk](../../modules/edge-api-sdk) which normalises the event format and offers some extra helpers. diff --git a/packages/constructs/edge-api/src/edge-api-lambda.ts b/packages/constructs/edge-api/src/edge-api-lambda.ts index 3e62dad..6621620 100644 --- a/packages/constructs/edge-api/src/edge-api-lambda.ts +++ b/packages/constructs/edge-api/src/edge-api-lambda.ts @@ -4,11 +4,19 @@ import { EdgeFunctionProps } from 'aws-cdk-lib/aws-cloudfront/lib/experimental' export class EdgeAPILambda extends Function { edgeEnvironment: FunctionProps['environment'] - constructor(scope: Construct, id: string, props: EdgeFunctionProps & { environment?: FunctionProps['environment'] }) { - const { environment, ...rest } = props + codePath?: string + + constructor( + scope: Construct, + id: string, + props: EdgeFunctionProps & { environment?: FunctionProps['environment']; codePath?: string }, + ) { + const { environment, codePath, ...rest } = props super(scope, id, rest) this.edgeEnvironment = environment + this.codePath = codePath } + addEnvironment(key: string, value: any): this { if (!this.edgeEnvironment) { this.edgeEnvironment = {} diff --git a/packages/constructs/edge-api/src/edge-api.ts b/packages/constructs/edge-api/src/edge-api.ts index af19b96..1c6664f 100644 --- a/packages/constructs/edge-api/src/edge-api.ts +++ b/packages/constructs/edge-api/src/edge-api.ts @@ -9,9 +9,12 @@ import { HttpApi } from '@aws-cdk/aws-apigatewayv2-alpha' export class EdgeAPI extends Construct { private api: ProductionEdgeAPI | DevEdgeAPI + route53Target: RecordTarget + _distribution?: Distribution _httpApi?: HttpApi + _endpoints: Endpoint[] constructor(scope: Construct, id: string, props: EdgeAPIProps) { super(scope, id) @@ -25,6 +28,7 @@ export class EdgeAPI extends Construct { if (props.devMode && props.webAclId) { console.warn('devMode enabled, ignoring webAclId') } + this._endpoints = [] this.api = props.devMode ? new DevEdgeAPI(this, 'api', props) : new ProductionEdgeAPI(this, 'api', props) this.route53Target = this.api.r53Target if (this.api instanceof ProductionEdgeAPI) { @@ -37,5 +41,6 @@ export class EdgeAPI extends Construct { addEndpoint(endpoint: Endpoint) { this.api.addEndpoint(endpoint) + this._endpoints.push(endpoint) } } diff --git a/packages/constructs/edge-api/src/types.ts b/packages/constructs/edge-api/src/types.ts index 9e590ee..e9d7f61 100644 --- a/packages/constructs/edge-api/src/types.ts +++ b/packages/constructs/edge-api/src/types.ts @@ -35,7 +35,7 @@ export const isRequestMiddleware = ( return !isResponseMiddleware(middleware) } -type Destination = string | Record +export type Destination = string | Record export type DisableBuiltInMiddlewares = { cookie?: boolean diff --git a/packages/constructs/replicated-secret/readme.md b/packages/constructs/replicated-secret/readme.md index 1d26e2f..4ec448f 100644 --- a/packages/constructs/replicated-secret/readme.md +++ b/packages/constructs/replicated-secret/readme.md @@ -3,7 +3,7 @@ ![npm version](https://img.shields.io/npm/v/@reapit-cdk/replicated-secret) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/replicated-secret) -![coverage: 97.17%25](https://img.shields.io/badge/coverage-97.17%25-green) +![coverage: 97.06%25](https://img.shields.io/badge/coverage-97.06%25-green) ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) Creates a Secret and replicates it across the given regions. Requires a [ReplicatedKey](../replicated-key/readme.md) be passed in. diff --git a/packages/modules/edge-api-sdk/readme.md b/packages/modules/edge-api-sdk/readme.md index f7167ee..b0c68b7 100644 --- a/packages/modules/edge-api-sdk/readme.md +++ b/packages/modules/edge-api-sdk/readme.md @@ -3,7 +3,7 @@ ![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api-sdk) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api-sdk) -![coverage: 96.44%25](https://img.shields.io/badge/coverage-96.44%25-green) +![coverage: 96.68%25](https://img.shields.io/badge/coverage-96.68%25-green) Provides convenience wrappers for accepting and responding to [@reapit-cdk/edge-api]('../../constructs/edge-api/readme.md') lambda requests. diff --git a/packages/modules/email-receiver-types/readme.md b/packages/modules/email-receiver-types/readme.md index acb3869..f13bf6e 100644 --- a/packages/modules/email-receiver-types/readme.md +++ b/packages/modules/email-receiver-types/readme.md @@ -5,6 +5,8 @@ ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/email-receiver-types) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) +Types for @reapit-cdk/email-receiver and client. + ## Package Installation: ```sh diff --git a/readme.md b/readme.md index 65a778e..3b37b8f 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ # @reapit-cdk/ts-constructs -![coverage: 95.81%25](https://img.shields.io/badge/coverage-95.81%25-green) +![coverage: 93.49%25](https://img.shields.io/badge/coverage-93.49%25-green) CDK Constructs Monorepo ## Constructs @@ -7,7 +7,7 @@ CDK Constructs Monorepo Packages -

@reapit-cdk/active-ruleset

+

@reapit-cdk/active-ruleset

![npm version](https://img.shields.io/npm/v/@reapit-cdk/active-ruleset) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/active-ruleset) @@ -15,7 +15,7 @@ CDK Constructs Monorepo ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) This construct returns the currently active SES receipt RuleSet, or creates one. This enables you to add rules to it. -

@reapit-cdk/cloudfront-invalidation

+

@reapit-cdk/cloudfront-invalidation

![npm version](https://img.shields.io/npm/v/@reapit-cdk/cloudfront-invalidation) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/cloudfront-invalidation) @@ -23,15 +23,31 @@ This construct returns the currently active SES receipt RuleSet, or creates one. ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) CloudFront invalidations are [very error prone](https://github.com/aws/aws-cdk/issues/15891#issuecomment-966456154), making it hard to invalidate distributions reliably. This construct aims to solve this problem by using a step function which is triggered on stack update, and uses exponential backoff to retry the invalidation. Inspired by https://github.com/aws/aws-cdk/issues/15891#issuecomment-1362163142. -

@reapit-cdk/edge-api

+

@reapit-cdk/cross-region-stack-export

+ +![npm version](https://img.shields.io/npm/v/@reapit-cdk/cross-region-stack-export) +![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/cross-region-stack-export) +![coverage: 74.02%25](https://img.shields.io/badge/coverage-74.02%25-orange) +![Integ Tests: X](https://img.shields.io/badge/Integ%20Tests-X-red) + +Allows you to share values between stack across regions and accounts. +

@reapit-cdk/edge-api

![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api) -![coverage: 97.85%25](https://img.shields.io/badge/coverage-97.85%25-green) +![coverage: 96.97%25](https://img.shields.io/badge/coverage-96.97%25-green) ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) This construct creates a truly globally available API where code executes at the edge. Because changes take a long time to propagate to all edge locations, there is a `devMode` flag which will instead deploy your API to a [HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html). This is compatible with hotswapping, so [`cdk watch`](https://docs.aws.amazon.com/cdk/v2/guide/cli.html#cli-deploy-watch) works very well. In order to make it easy to develop APIs which handle both event formats and work around the environment variable limitation, I recommend you use the lightweight request wrapper [@reapit-cdk/edge-api-sdk](../../modules/edge-api-sdk) which normalises the event format and offers some extra helpers. -

@reapit-cdk/email-receiver

+

@reapit-cdk/edge-api-swagger

+ +![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api-swagger) +![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api-swagger) +![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) +![Integ Tests: X](https://img.shields.io/badge/Integ%20Tests-X-red) + +Add a swagger endpoint to your EdgeAPI +

@reapit-cdk/email-receiver

![npm version](https://img.shields.io/npm/v/@reapit-cdk/email-receiver) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/email-receiver) @@ -39,7 +55,7 @@ This construct creates a truly globally available API where code executes at the ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) This construct sets up everything necessary to receive email. The emails get stored in a dynamodb table, queryable by recipient. This is designed to be used in end-to-end tests, with the [@reapit-cdk/email-receiver-client](../../libs/email-receiver-client) helper library. -

@reapit-cdk/replicated-key

+

@reapit-cdk/replicated-key

![npm version](https://img.shields.io/npm/v/@reapit-cdk/replicated-key) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/replicated-key) @@ -47,15 +63,15 @@ This construct sets up everything necessary to receive email. The emails get sto ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) Creates a KMS key and replicates it to the desired regions. Useful when replicating secrets across regions. -

@reapit-cdk/replicated-secret

+

@reapit-cdk/replicated-secret

![npm version](https://img.shields.io/npm/v/@reapit-cdk/replicated-secret) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/replicated-secret) -![coverage: 97.17%25](https://img.shields.io/badge/coverage-97.17%25-green) +![coverage: 97.06%25](https://img.shields.io/badge/coverage-97.06%25-green) ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) Creates a Secret and replicates it across the given regions. Requires a [ReplicatedKey](../replicated-key/readme.md) be passed in. -

@reapit-cdk/userpool-domain

+

@reapit-cdk/userpool-domain

![npm version](https://img.shields.io/npm/v/@reapit-cdk/userpool-domain) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/userpool-domain) @@ -63,7 +79,7 @@ Creates a Secret and replicates it across the given regions. Requires a [Replica ![Integ Tests: ✔](https://img.shields.io/badge/Integ%20Tests-%E2%9C%94-green) This construct returns the given Cognito UserPool's UserPoolDomain, or creates one. This resolves an issue with [AWS::Cognito::UserPoolDomain](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpooldomain.html), since that will fail if one already exists. -

@reapit-cdk/wildcard-certificate

+

@reapit-cdk/wildcard-certificate

![npm version](https://img.shields.io/npm/v/@reapit-cdk/wildcard-certificate) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/wildcard-certificate) @@ -81,41 +97,34 @@ This construct returns a wildcard certificate valid for subdomains of the given Packages -

@reapit-cdk/common

- -![npm version](https://img.shields.io/npm/v/@reapit-cdk/common) -![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/common) -![coverage: 99.02%25](https://img.shields.io/badge/coverage-99.02%25-green) - -Common types and small utilities. -

@reapit-cdk/custom-resource-wrapper

+

@reapit-cdk/custom-resource-wrapper

![npm version](https://img.shields.io/npm/v/@reapit-cdk/custom-resource-wrapper) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/custom-resource-wrapper) ![coverage: 99.02%25](https://img.shields.io/badge/coverage-99.02%25-green) This module helps write custom resource handlers. It's designed to work with the [Custom Resource Provider Framework](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.CustomResource.html). It accepts an object which contains event handlers for `onCreate`, and optionally, `onUpdate`, and `onDelete`. Anything returned from `onCreate` and `onUpdate` is returned as data attributes on the resulting custom resource. -

@reapit-cdk/edge-api-sdk

+

@reapit-cdk/edge-api-sdk

![npm version](https://img.shields.io/npm/v/@reapit-cdk/edge-api-sdk) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/edge-api-sdk) -![coverage: 96.44%25](https://img.shields.io/badge/coverage-96.44%25-green) +![coverage: 96.68%25](https://img.shields.io/badge/coverage-96.68%25-green) Provides convenience wrappers for accepting and responding to [@reapit-cdk/edge-api]('../../constructs/edge-api/readme.md') lambda requests. -

@reapit-cdk/email-receiver-client

+

@reapit-cdk/email-receiver-client

![npm version](https://img.shields.io/npm/v/@reapit-cdk/email-receiver-client) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/email-receiver-client) ![coverage: 99.02%25](https://img.shields.io/badge/coverage-99.02%25-green) This module helps you write tests which rely on receiving emails. Once you have set up [@reapit-cdk/email-receiver](../../constructs/email-receiver/), this module helps you interact with the dynamodb table it creates. You'll have to export the table arn and domain name from your stack and import them to be used here, using [something like this](https://gist.github.com/joshbalfour/c0deb95f1e5938434ed6f6117dec8662). -

@reapit-cdk/email-receiver-types

+

@reapit-cdk/email-receiver-types

![npm version](https://img.shields.io/npm/v/@reapit-cdk/email-receiver-types) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/email-receiver-types) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) - +Types for @reapit-cdk/email-receiver and client. @@ -126,49 +135,49 @@ This module helps you write tests which rely on receiving emails. Once you have Packages -

@reapit-cdk/eslint-config

+

@reapit-cdk/eslint-config

![npm version](https://img.shields.io/npm/v/@reapit-cdk/eslint-config) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/eslint-config) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) @reapit-cdk eslint config. -

@reapit-cdk/generate-readme

+

@reapit-cdk/generate-readme

![npm version](https://img.shields.io/npm/v/@reapit-cdk/generate-readme) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/generate-readme) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) Generates package readmes. -

@reapit-cdk/integration-tests

+

@reapit-cdk/integration-tests

![npm version](https://img.shields.io/npm/v/@reapit-cdk/integration-tests) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/integration-tests) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) Easily run integration tests for CDK constructs using Jest. On successful test suite run, snapshots the stack which gets stored in your repo alongside the test. Subsequent test runs will diff the stack against the snapshot, and only run the tests if something changes. -

@reapit-cdk/jsii

+

@reapit-cdk/jsii

![npm version](https://img.shields.io/npm/v/@reapit-cdk/jsii) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/jsii) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) JSII tools for @reapit-cdk. -

@reapit-cdk/tsconfig

+

@reapit-cdk/tsconfig

![npm version](https://img.shields.io/npm/v/@reapit-cdk/tsconfig) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/tsconfig) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) tsconfig for @reapit-cdk. -

@reapit-cdk/tsup

+

@reapit-cdk/tsup

![npm version](https://img.shields.io/npm/v/@reapit-cdk/tsup) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/tsup) ![coverage: 0%25](https://img.shields.io/badge/coverage-0%25-red) Easily build @reapit-cdk constructs and custom resource lambdas. -

@reapit-cdk/version-package

+

@reapit-cdk/version-package

![npm version](https://img.shields.io/npm/v/@reapit-cdk/version-package) ![npm downloads](https://img.shields.io/npm/dm/@reapit-cdk/version-package) diff --git a/yarn.lock b/yarn.lock index 6a27874..dcc95dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2866,15 +2866,33 @@ __metadata: languageName: unknown linkType: soft +"@reapit-cdk/edge-api-swagger@workspace:packages/constructs/edge-api-swagger": + version: 0.0.0-use.local + resolution: "@reapit-cdk/edge-api-swagger@workspace:packages/constructs/edge-api-swagger" + dependencies: + "@reapit-cdk/eslint-config": "workspace:^" + "@reapit-cdk/integration-tests": "workspace:^" + "@reapit-cdk/jsii": "workspace:^" + "@reapit-cdk/tsup": "workspace:^" + "@reapit-cdk/version-package": "workspace:^" + aws-cdk-lib: "npm:^2.96.2" + constructs: "npm:^10.2.70" + openapi3-ts: "npm:^4.1.2" + swagger-ui-dist: "npm:^5.9.0" + typescript: "npm:^5.1.3" + peerDependencies: + "@reapit-cdk/edge-api": "workspace:^" + aws-cdk-lib: ^2.96.2 + constructs: ^10.2.70 + languageName: unknown + linkType: soft + "@reapit-cdk/edge-api@workspace:packages/constructs/edge-api": version: 0.0.0-use.local resolution: "@reapit-cdk/edge-api@workspace:packages/constructs/edge-api" dependencies: "@aws-cdk/aws-apigatewayv2-alpha": "npm:2.96.2-alpha.0" "@aws-cdk/aws-apigatewayv2-integrations-alpha": "npm:2.96.2-alpha.0" - "@aws-sdk/client-dynamodb": "npm:3.414.0" - "@aws-sdk/client-secrets-manager": "npm:3.414.0" - "@aws-sdk/lib-dynamodb": "npm:3.414.0" "@reapit-cdk/cloudfront-invalidation": "workspace:^" "@reapit-cdk/eslint-config": "workspace:^" "@reapit-cdk/integration-tests": "workspace:^" @@ -3050,6 +3068,7 @@ __metadata: "@reapit-cdk/generate-readme": "workspace:^" "@reapit-cdk/integration-tests": "workspace:^" "@types/jest": "npm:^29.5.5" + "@types/swagger-ui-dist": "npm:^3.30.3" aws-cdk: "npm:2.100.0" aws-sdk-client-mock: "npm:^3.0.0" aws-sdk-client-mock-jest: "npm:^3.0.0" @@ -4393,6 +4412,13 @@ __metadata: languageName: node linkType: hard +"@types/swagger-ui-dist@npm:^3.30.3": + version: 3.30.3 + resolution: "@types/swagger-ui-dist@npm:3.30.3" + checksum: fcfdee4118eb44257cbe23bdab31e0314e23e2258b2f62f36b5a6401d1e13c94dbc5e2722fcda5d86e9e050587b4e4a9854d78b6af20f66ae126729baad18034 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -8401,6 +8427,15 @@ __metadata: languageName: node linkType: hard +"openapi3-ts@npm:^4.1.2": + version: 4.1.2 + resolution: "openapi3-ts@npm:4.1.2" + dependencies: + yaml: "npm:^2.2.2" + checksum: 21033234896168ee73aabf286e166872796c270a779d771ad80a938c8ad8aee2f14e0649b7fac75c0d671f21af0f2959613a9cc31936e9925c963229f4f2f6a5 + languageName: node + linkType: hard + "optionator@npm:^0.9.3": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -9396,6 +9431,13 @@ __metadata: languageName: node linkType: hard +"swagger-ui-dist@npm:^5.9.0": + version: 5.9.0 + resolution: "swagger-ui-dist@npm:5.9.0" + checksum: f6dd834e59be7b2b8cec2205d31569784956aa9e7ad8daad1bdfc684c8584b26b0568f52061cec57854a21456cc1f474f49e1e56aecd4d5c6c762e1bcd805a21 + languageName: node + linkType: hard + "synckit@npm:^0.8.5": version: 0.8.5 resolution: "synckit@npm:0.8.5" @@ -9693,7 +9735,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.2.2, typescript@npm:~5.2": +"typescript@npm:^5.1.3, typescript@npm:^5.2.2, typescript@npm:~5.2": version: 5.2.2 resolution: "typescript@npm:5.2.2" bin: @@ -9723,7 +9765,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A~5.2#optional!builtin": +"typescript@patch:typescript@npm%3A^5.1.3#optional!builtin, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin, typescript@patch:typescript@npm%3A~5.2#optional!builtin": version: 5.2.2 resolution: "typescript@patch:typescript@npm%3A5.2.2#optional!builtin::version=5.2.2&hash=f3b441" bin: @@ -10063,6 +10105,13 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.2": + version: 2.3.3 + resolution: "yaml@npm:2.3.3" + checksum: 3b1a974b9d3672c671d47099a41c0de77b7ff978d0849aa55a095587486e82cd072321d19f2b4c791a367f766310b5a82dff098839b0f4ddcbbbe477f82dfb07 + languageName: node + linkType: hard + "yargs-parser@npm:^20.2.2": version: 20.2.9 resolution: "yargs-parser@npm:20.2.9"