diff --git a/package-lock.json b/package-lock.json index e7838e2810..f28c9ebbfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2412,7 +2412,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" diff --git a/packages/bruno-schema/.gitignore b/packages/bruno-schema/.gitignore index 024348ee56..50447684ed 100644 --- a/packages/bruno-schema/.gitignore +++ b/packages/bruno-schema/.gitignore @@ -5,3 +5,5 @@ out pnpm-lock.yaml package-lock.json yarn.lock + +/dist diff --git a/packages/bruno-schema/package.json b/packages/bruno-schema/package.json index 61542cf698..6b966446f5 100644 --- a/packages/bruno-schema/package.json +++ b/packages/bruno-schema/package.json @@ -2,18 +2,34 @@ "name": "@usebruno/schema", "version": "0.7.0", "license": "MIT", - "main": "src/index.js", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "type": "commonjs", "files": [ + "dist", "src", "package.json" ], "scripts": { - "test": "jest" + "clean": "rimraf dist", + "prebuild": "npm run clean", + "build": "tsc", + "test": "jest", + "typecheck": "tsc --noEmit", + "prepack": "npm run build" + }, + "devDependencies": { + "@types/yup": "^0.29.14", + "rimraf": "^6.0.1", + "typescript": "^5.8.3" }, "peerDependencies": { "yup": "^0.32.11" }, "dependencies": { "nanoid": "3.3.8" + }, + "jest": { + "preset": "ts-jest" } -} +} \ No newline at end of file diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js deleted file mode 100644 index 908822fd2f..0000000000 --- a/packages/bruno-schema/src/collections/index.js +++ /dev/null @@ -1,625 +0,0 @@ -const Yup = require('yup'); -const { uidSchema } = require('../common'); - -const environmentVariablesSchema = Yup.object({ - uid: uidSchema, - name: Yup.string().nullable(), - // Allow mixed types (string, number, boolean, object) to support setting non-string values via scripts. - value: Yup.mixed().nullable(), - type: Yup.string().oneOf(['text']).required('type is required'), - enabled: Yup.boolean().defined(), - secret: Yup.boolean() -}) - .noUnknown(true) - .strict(); - -const environmentSchema = Yup.object({ - uid: uidSchema, - name: Yup.string().min(1).required('name is required'), - variables: Yup.array().of(environmentVariablesSchema).required('variables are required') -}) - .noUnknown(true) - .strict(); - -const environmentsSchema = Yup.array().of(environmentSchema); - -const keyValueSchema = Yup.object({ - uid: uidSchema, - name: Yup.string().nullable(), - value: Yup.string().nullable(), - description: Yup.string().nullable(), - enabled: Yup.boolean() -}) - .noUnknown(true) - .strict(); - -const assertionOperators = [ - 'eq', - 'neq', - 'gt', - 'gte', - 'lt', - 'lte', - 'in', - 'notIn', - 'contains', - 'notContains', - 'length', - 'matches', - 'notMatches', - 'startsWith', - 'endsWith', - 'between', - 'isEmpty', - 'isNotEmpty', - 'isNull', - 'isUndefined', - 'isDefined', - 'isTruthy', - 'isFalsy', - 'isJson', - 'isNumber', - 'isString', - 'isBoolean', - 'isArray' -]; - -const assertionSchema = keyValueSchema.shape({ - operator: Yup.string() - .oneOf(assertionOperators) - .nullable() - .optional() -}) - .noUnknown(true) - .strict(); - -const varsSchema = Yup.object({ - uid: uidSchema, - name: Yup.string().nullable(), - value: Yup.string().nullable(), - description: Yup.string().nullable(), - enabled: Yup.boolean(), - - // todo - // anoop(4 feb 2023) - nobody uses this, and it needs to be removed - local: Yup.boolean() -}) - .noUnknown(true) - .strict(); - -const requestUrlSchema = Yup.string().min(0).defined(); -const requestMethodSchema = Yup.string() - .min(1, 'method is required') - .required('method is required'); - -const graphqlBodySchema = Yup.object({ - query: Yup.string().nullable(), - variables: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const multipartFormSchema = Yup.object({ - uid: uidSchema, - type: Yup.string().oneOf(['file', 'text']).required('type is required'), - name: Yup.string().nullable(), - value: Yup.mixed().when('type', { - is: 'file', - then: Yup.array().of(Yup.string().nullable()).nullable(), - otherwise: Yup.string().nullable() - }), - description: Yup.string().nullable(), - contentType: Yup.string().nullable(), - enabled: Yup.boolean() -}) - .noUnknown(true) - .strict(); - - -const fileSchema = Yup.object({ - uid: uidSchema, - filePath: Yup.string().nullable(), - contentType: Yup.string().nullable(), - selected: Yup.boolean() -}) - .noUnknown(true) - .strict(); - -const requestBodySchema = Yup.object({ - mode: Yup.string() - .oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm', 'graphql', 'sparql', 'file']) - .required('mode is required'), - json: Yup.string().nullable(), - text: Yup.string().nullable(), - xml: Yup.string().nullable(), - sparql: Yup.string().nullable(), - formUrlEncoded: Yup.array().of(keyValueSchema).nullable(), - multipartForm: Yup.array().of(multipartFormSchema).nullable(), - graphql: graphqlBodySchema.nullable(), - file: Yup.array().of(fileSchema).nullable() -}) - .noUnknown(true) - .strict(); - -const authAwsV4Schema = Yup.object({ - accessKeyId: Yup.string().nullable(), - secretAccessKey: Yup.string().nullable(), - sessionToken: Yup.string().nullable(), - service: Yup.string().nullable(), - region: Yup.string().nullable(), - profileName: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const authBasicSchema = Yup.object({ - username: Yup.string().nullable(), - password: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const authWsseSchema = Yup.object({ - username: Yup.string().nullable(), - password: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const authBearerSchema = Yup.object({ - token: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const authDigestSchema = Yup.object({ - username: Yup.string().nullable(), - password: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - - - - const authNTLMSchema = Yup.object({ - username: Yup.string().nullable(), - password: Yup.string().nullable(), - domain: Yup.string().nullable() - - }) - .noUnknown(true) - .strict(); - -const authApiKeySchema = Yup.object({ - key: Yup.string().nullable(), - value: Yup.string().nullable(), - placement: Yup.string().oneOf(['header', 'queryparams']).nullable() -}) - .noUnknown(true) - .strict(); - -const oauth2AuthorizationAdditionalParametersSchema = Yup.object({ - name: Yup.string().nullable(), - value: Yup.string().nullable(), - sendIn: Yup.string() - .oneOf(['headers', 'queryparams']) - .required('send in property is required'), - enabled: Yup.boolean() -}) - .noUnknown(true) - .strict(); - -const oauth2AdditionalParametersSchema = Yup.object({ - name: Yup.string().nullable(), - value: Yup.string().nullable(), - sendIn: Yup.string() - .oneOf(['headers', 'queryparams', 'body']) - .required('send in property is required'), - enabled: Yup.boolean() - }) - .noUnknown(true) - .strict(); - -const oauth2Schema = Yup.object({ - grantType: Yup.string() - .oneOf(['client_credentials', 'password', 'authorization_code', 'implicit']) - .required('grantType is required'), - username: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - password: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - callbackUrl: Yup.string().when('grantType', { - is: (val) => ['authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - authorizationUrl: Yup.string().when('grantType', { - is: (val) => ['authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - accessTokenUrl: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - clientId: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - clientSecret: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - scope: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - state: Yup.string().when('grantType', { - is: (val) => ['authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - pkce: Yup.boolean().when('grantType', { - is: (val) => ['authorization_code'].includes(val), - then: Yup.boolean().default(false), - otherwise: Yup.boolean() - }), - credentialsPlacement: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - credentialsId: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - tokenPlacement: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - tokenHeaderPrefix: Yup.string().when(['grantType', 'tokenPlacement'], { - is: (grantType, tokenPlacement) => - ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(grantType) && tokenPlacement === 'header', - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - tokenQueryKey: Yup.string().when(['grantType', 'tokenPlacement'], { - is: (grantType, tokenPlacement) => - ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(grantType) && tokenPlacement === 'url', - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - refreshTokenUrl: Yup.string().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), - then: Yup.string().nullable(), - otherwise: Yup.string().nullable().strip() - }), - autoRefreshToken: Yup.boolean().when('grantType', { - is: (val) => ['client_credentials', 'password', 'authorization_code'].includes(val), - then: Yup.boolean().default(false), - otherwise: Yup.boolean() - }), - autoFetchToken: Yup.boolean().when('grantType', { - is: (val) => ['authorization_code', 'implicit'].includes(val), - then: Yup.boolean().default(true), - otherwise: Yup.boolean() - }), - additionalParameters: Yup.object({ - authorization: Yup.mixed().when('grantType', { - is: 'authorization_code', - then: Yup.array().of(oauth2AuthorizationAdditionalParametersSchema).required(), - otherwise: Yup.mixed().nullable().optional() - }), - token: Yup.array().of(oauth2AdditionalParametersSchema).optional(), - refresh: Yup.array().of(oauth2AdditionalParametersSchema).optional() - }) -}) - .noUnknown(true) - .strict(); - -const authSchema = Yup.object({ - mode: Yup.string() - .oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth2', 'wsse', 'apikey']) - .required('mode is required'), - awsv4: authAwsV4Schema.nullable(), - basic: authBasicSchema.nullable(), - bearer: authBearerSchema.nullable(), - ntlm: authNTLMSchema.nullable(), - digest: authDigestSchema.nullable(), - oauth2: oauth2Schema.nullable(), - wsse: authWsseSchema.nullable(), - apikey: authApiKeySchema.nullable() -}) - .noUnknown(true) - .strict() - .nullable(); - -const requestParamsSchema = Yup.object({ - uid: uidSchema, - name: Yup.string().nullable(), - value: Yup.string().nullable(), - description: Yup.string().nullable(), - type: Yup.string().oneOf(['query', 'path']).required('type is required'), - enabled: Yup.boolean() -}) - .noUnknown(true) - .strict(); - -const exampleSchema = Yup.object({ - uid: uidSchema, - itemUid: uidSchema, - name: Yup.string().min(1, 'name must be at least 1 character').required('name is required'), - description: Yup.string().nullable(), - type: Yup.string().oneOf(['http-request', 'graphql-request', 'grpc-request',]).required('type is required'), - request: Yup.object({ - url: requestUrlSchema, - method: requestMethodSchema, - headers: Yup.array().of(keyValueSchema).required('headers are required'), - params: Yup.array().of(requestParamsSchema).required('params are required'), - body: requestBodySchema - }) - .noUnknown(true) - .strict() - .nullable(), - response: Yup.object({ - status: Yup.string().nullable(), - statusText: Yup.string().nullable(), - headers: Yup.array().of(keyValueSchema).nullable(), - body: Yup.object({ - type: Yup.string().oneOf(['json', 'text', 'xml', 'html', 'binary']).nullable(), - content: Yup.mixed().nullable() - }).nullable() - }) - .noUnknown(true) - .strict() - .nullable() -}) - .noUnknown(true) - .strict(); - -// Right now, the request schema is very tightly coupled with http request -// As we introduce more request types in the future, we will improve the definition to support -// schema structure based on other request type -const requestSchema = Yup.object({ - url: requestUrlSchema, - method: requestMethodSchema, - headers: Yup.array().of(keyValueSchema).required('headers are required'), - params: Yup.array().of(requestParamsSchema).required('params are required'), - auth: authSchema, - body: requestBodySchema, - script: Yup.object({ - req: Yup.string().nullable(), - res: Yup.string().nullable() - }) - .noUnknown(true) - .strict(), - vars: Yup.object({ - req: Yup.array().of(varsSchema).nullable(), - res: Yup.array().of(varsSchema).nullable() - }) - .noUnknown(true) - .strict() - .nullable(), - assertions: Yup.array().of(assertionSchema).nullable(), - tests: Yup.string().nullable(), - docs: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const grpcRequestSchema = Yup.object({ - url: requestUrlSchema, - method: Yup.string().optional(), - methodType: Yup.string().oneOf(['unary', 'client-streaming', 'server-streaming', 'bidi-streaming', '']).nullable(), - protoPath: Yup.string().nullable(), - headers: Yup.array().of(keyValueSchema).required('headers are required'), - auth: authSchema, - body: Yup.object({ - mode: Yup.string().oneOf(['grpc']).required('mode is required'), - grpc: Yup.array().of(Yup.object({ - name: Yup.string().nullable(), - content: Yup.string().nullable() - })).nullable() - }) - .strict() - .required('body is required'), - script: Yup.object({ - req: Yup.string().nullable(), - res: Yup.string().nullable() - }) - .noUnknown(true) - .strict(), - vars: Yup.object({ - req: Yup.array().of(varsSchema).nullable(), - res: Yup.array().of(varsSchema).nullable() - }) - .noUnknown(true) - .strict() - .nullable(), - assertions: Yup.array().of(assertionSchema).nullable(), - tests: Yup.string().nullable(), - docs: Yup.string().nullable(), -}) - .noUnknown(true) - .strict(); - -const wsRequestSchema = Yup.object({ - url: requestUrlSchema, - headers: Yup.array().of(keyValueSchema).required('headers are required'), - auth: authSchema, - body: Yup.object({ - mode: Yup.string().oneOf(['ws']).required('mode is required'), - ws: Yup.array() - .of( - Yup.object({ - name: Yup.string().nullable(), - type: Yup.string().nullable(), - content: Yup.string().nullable() - }) - ) - .nullable() - }) - .strict() - .required('body is required'), - script: Yup.object({ - req: Yup.string().nullable(), - res: Yup.string().nullable() - }) - .noUnknown(true) - .strict(), - vars: Yup.object({ - req: Yup.array().of(varsSchema).nullable(), - res: Yup.array().of(varsSchema).nullable() - }) - .noUnknown(true) - .strict() - .nullable(), - assertions: Yup.array().of(assertionSchema).nullable(), - tests: Yup.string().nullable(), - docs: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const wsSettingsSchema = Yup.object({ - settings: Yup.object({ - timeout: Yup.number() - .default(500), - keepAliveInterval: Yup.number() - .default(0) - }).noUnknown(true) - .strict() - .nullable() -}); - -const folderRootSchema = Yup.object({ - request: Yup.object({ - headers: Yup.array().of(keyValueSchema).nullable(), - auth: authSchema, - script: Yup.object({ - req: Yup.string().nullable(), - res: Yup.string().nullable() - }) - .noUnknown(true) - .strict() - .nullable(), - vars: Yup.object({ - req: Yup.array().of(varsSchema).nullable(), - res: Yup.array().of(varsSchema).nullable() - }) - .noUnknown(true) - .strict() - .nullable(), - tests: Yup.string().nullable() - }) - .noUnknown(true) - .strict() - .nullable(), - docs: Yup.string().nullable(), - meta: Yup.object({ - name: Yup.string().nullable(), - seq: Yup.number().min(1).nullable() - }) - .noUnknown(true) - .strict() - .nullable() -}) - .noUnknown(true) - .nullable(); - -const itemSchema = Yup.object({ - uid: uidSchema, - type: Yup.string().oneOf(['http-request', 'graphql-request', 'folder', 'js', 'grpc-request', 'ws-request']).required('type is required'), - seq: Yup.number().min(1), - name: Yup.string().min(1, 'name must be at least 1 character').required('name is required'), - tags: Yup.array().of(Yup.string().matches(/^[\w-]+$/, 'tag must be alphanumeric')), - request: Yup.mixed().when('type', { - is: (type) => type === 'grpc-request', - then: grpcRequestSchema.required('request is required when item-type is grpc-request'), - otherwise: Yup.mixed().when('type', { - is: (type) => type === 'ws-request', - then: wsRequestSchema.required('request is required when item-type is ws-request'), - otherwise: requestSchema.when('type', { - is: (type) => ['http-request', 'graphql-request'].includes(type), - then: (schema) => schema.required('request is required when item-type is request') - }) - }) - }), - settings: Yup.mixed() - .when('type', { - is: (type) => type === 'ws-request', - then: wsSettingsSchema, - otherwise: Yup.object({ - encodeUrl: Yup.boolean().nullable(), - followRedirects: Yup.boolean().nullable(), - maxRedirects: Yup.number().min(0).max(50).nullable(), - timeout: Yup.mixed().nullable(), - }).noUnknown(true) - .strict() - .nullable() - }), - fileContent: Yup.string().when('type', { - // If the type is 'js', the fileContent field is expected to be a string. - // This can include an empty string, indicating that the JS file may not have any content. - is: 'js', - then: Yup.string(), - // For all other types, the fileContent field is not required and can be null. - otherwise: Yup.string().nullable() - }), - root: Yup.mixed().when('type', { - is: 'folder', - then: folderRootSchema, - otherwise: Yup.mixed().nullable().notRequired() - }), - items: Yup.lazy(() => Yup.array().of(itemSchema)), - examples: Yup.array().of(exampleSchema).when('type', { - is: (type) => ['http-request', 'graphql-request', 'grpc-request'].includes(type), - then: (schema) => schema.nullable(), - otherwise: Yup.array().strip() - }), - filename: Yup.string().nullable(), - pathname: Yup.string().nullable() -}) - .noUnknown(true) - .strict(); - -const collectionSchema = Yup.object({ - version: Yup.string().oneOf(['1']).required('version is required'), - uid: uidSchema, - name: Yup.string().min(1, 'name must be at least 1 character').required('name is required'), - items: Yup.array().of(itemSchema), - activeEnvironmentUid: Yup.string() - .length(21, 'activeEnvironmentUid must be 21 characters in length') - .matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric') - .nullable(), - environments: environmentsSchema, - pathname: Yup.string().nullable(), - runnerResult: Yup.object({ - items: Yup.array() - }), - runtimeVariables: Yup.object(), - brunoConfig: Yup.object(), - root: folderRootSchema -}) - .noUnknown(true) - .strict(); - -module.exports = { - requestSchema, - itemSchema, - environmentSchema, - environmentsSchema, - collectionSchema -}; diff --git a/packages/bruno-schema/src/collections/index.spec.js b/packages/bruno-schema/src/collections/index.spec.ts similarity index 95% rename from packages/bruno-schema/src/collections/index.spec.js rename to packages/bruno-schema/src/collections/index.spec.ts index f15031c4cf..3b98b28c1d 100644 --- a/packages/bruno-schema/src/collections/index.spec.js +++ b/packages/bruno-schema/src/collections/index.spec.ts @@ -1,6 +1,6 @@ -const { expect } = require('@jest/globals'); -const { uuid } = require('../utils/testUtils'); -const { collectionSchema } = require('./index'); +import { expect } from '@jest/globals'; +import { uuid } from '../utils/testUtils'; +import { collectionSchema } from './index'; describe('Collection Schema Validation', () => { it('collection schema must validate successfully - simple collection, no items', async () => { @@ -136,7 +136,7 @@ describe('Collection Schema Validation', () => { ] }; - expect(collectionSchema.validate(collection)).rejects.toThrow('items[0].request field has unspecified keys: params'); + await expect(collectionSchema.validate(collection)).rejects.toThrow('items[0].request field has unspecified keys: params'); }); diff --git a/packages/bruno-schema/src/collections/index.ts b/packages/bruno-schema/src/collections/index.ts new file mode 100644 index 0000000000..bb2a7597c6 --- /dev/null +++ b/packages/bruno-schema/src/collections/index.ts @@ -0,0 +1,657 @@ +import * as yup from 'yup'; +import { uidSchema } from '../common'; + +const environmentVariablesSchema = yup.object({ + uid: uidSchema, + name: yup.string().nullable(), + value: yup.mixed().nullable(), + type: yup.string().oneOf(['text']).required('type is required'), + enabled: yup.boolean().defined(), + secret: yup.boolean() +}) + .noUnknown(true) + .strict(); + +export const environmentSchema = yup.object({ + uid: uidSchema, + name: yup.string().min(1).required('name is required'), + variables: yup.array().of(environmentVariablesSchema).required('variables are required') +}) + .noUnknown(true) + .strict(); + +export const environmentsSchema = yup.array().of(environmentSchema); + +const keyValueSchema = yup.object({ + uid: uidSchema, + name: yup.string().nullable(), + value: yup.string().nullable(), + description: yup.string().nullable(), + enabled: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const assertionOperators: string[] = [ + 'eq', + 'neq', + 'gt', + 'gte', + 'lt', + 'lte', + 'in', + 'notIn', + 'contains', + 'notContains', + 'length', + 'matches', + 'notMatches', + 'startsWith', + 'endsWith', + 'between', + 'isEmpty', + 'isNotEmpty', + 'isNull', + 'isUndefined', + 'isDefined', + 'isTruthy', + 'isFalsy', + 'isJson', + 'isNumber', + 'isString', + 'isBoolean', + 'isArray' +]; + +const assertionSchema = keyValueSchema.shape({ + operator: yup + .string() + .oneOf(assertionOperators) + .nullable() + .optional() +}) + .noUnknown(true) + .strict(); + +const varsSchema = yup.object({ + uid: uidSchema, + name: yup.string().nullable(), + value: yup.string().nullable(), + description: yup.string().nullable(), + enabled: yup.boolean(), + local: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const requestUrlSchema = yup.string().min(0).defined(); +const requestMethodSchema = yup + .string() + .min(1, 'method is required') + .required('method is required'); + +const graphqlBodySchema = yup.object({ + query: yup.string().nullable(), + variables: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const multipartFormSchema = yup.object({ + uid: uidSchema, + type: yup.string().oneOf(['file', 'text']).required('type is required'), + name: yup.string().nullable(), + value: yup.mixed().when('type', { + is: 'file', + then: yup.array().of(yup.string().nullable()).nullable(), + otherwise: yup.string().nullable() + }), + description: yup.string().nullable(), + contentType: yup.string().nullable(), + enabled: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const fileSchema = yup.object({ + uid: uidSchema, + filePath: yup.string().nullable(), + contentType: yup.string().nullable(), + selected: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const requestBodySchema = yup.object({ + mode: yup + .string() + .oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm', 'graphql', 'sparql', 'file']) + .required('mode is required'), + json: yup.string().nullable(), + text: yup.string().nullable(), + xml: yup.string().nullable(), + sparql: yup.string().nullable(), + formUrlEncoded: yup.array().of(keyValueSchema).nullable(), + multipartForm: yup.array().of(multipartFormSchema).nullable(), + graphql: graphqlBodySchema.nullable(), + file: yup.array().of(fileSchema).nullable() +}) + .noUnknown(true) + .strict(); + +const authAwsV4Schema = yup.object({ + accessKeyId: yup.string().nullable(), + secretAccessKey: yup.string().nullable(), + sessionToken: yup.string().nullable(), + service: yup.string().nullable(), + region: yup.string().nullable(), + profileName: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authBasicSchema = yup.object({ + username: yup.string().nullable(), + password: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authWsseSchema = yup.object({ + username: yup.string().nullable(), + password: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authBearerSchema = yup.object({ + token: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authDigestSchema = yup.object({ + username: yup.string().nullable(), + password: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authNTLMSchema = yup.object({ + username: yup.string().nullable(), + password: yup.string().nullable(), + domain: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const authApiKeySchema = yup.object({ + key: yup.string().nullable(), + value: yup.string().nullable(), + placement: yup.string().oneOf(['header', 'queryparams']).nullable() +}) + .noUnknown(true) + .strict(); + +const oauth2AuthorizationAdditionalParametersSchema = yup.object({ + name: yup.string().nullable(), + value: yup.string().nullable(), + sendIn: yup + .string() + .oneOf(['headers', 'queryparams']) + .required('send in property is required'), + enabled: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const oauth2AdditionalParametersSchema = yup.object({ + name: yup.string().nullable(), + value: yup.string().nullable(), + sendIn: yup + .string() + .oneOf(['headers', 'queryparams', 'body']) + .required('send in property is required'), + enabled: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const oauth2Schema = yup.object({ + grantType: yup + .string() + .oneOf(['client_credentials', 'password', 'authorization_code', 'implicit']) + .required('grantType is required'), + username: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + password: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + callbackUrl: yup.string().when('grantType', { + is: (val: unknown): val is string => ['authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + authorizationUrl: yup.string().when('grantType', { + is: (val: unknown): val is string => ['authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + accessTokenUrl: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + clientId: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + clientSecret: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + scope: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + state: yup.string().when('grantType', { + is: (val: unknown): val is string => ['authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + pkce: yup.boolean().when('grantType', { + is: (val: unknown): val is string => ['authorization_code'].includes(val as string), + then: yup.boolean().default(false), + otherwise: yup.boolean() + }), + credentialsPlacement: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + credentialsId: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + tokenPlacement: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + tokenHeaderPrefix: yup.string().when(['grantType', 'tokenPlacement'], { + is: (grantType: unknown, tokenPlacement: unknown): boolean => + ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(grantType as string) && + tokenPlacement === 'header', + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + tokenQueryKey: yup.string().when(['grantType', 'tokenPlacement'], { + is: (grantType: unknown, tokenPlacement: unknown): boolean => + ['client_credentials', 'password', 'authorization_code', 'implicit'].includes(grantType as string) && + tokenPlacement === 'url', + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + refreshTokenUrl: yup.string().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code'].includes(val as string), + then: yup.string().nullable(), + otherwise: yup.string().nullable().strip() + }), + autoRefreshToken: yup.boolean().when('grantType', { + is: (val: unknown): val is string => ['client_credentials', 'password', 'authorization_code'].includes(val as string), + then: yup.boolean().default(false), + otherwise: yup.boolean() + }), + autoFetchToken: yup.boolean().when('grantType', { + is: (val: unknown): val is string => ['authorization_code', 'implicit'].includes(val as string), + then: yup.boolean().default(true), + otherwise: yup.boolean() + }), + additionalParameters: yup.object({ + authorization: yup.mixed().when('grantType', { + is: 'authorization_code', + then: yup.array().of(oauth2AuthorizationAdditionalParametersSchema).required(), + otherwise: yup.mixed().nullable().optional() + }), + token: yup.array().of(oauth2AdditionalParametersSchema).optional(), + refresh: yup.array().of(oauth2AdditionalParametersSchema).optional() + }) +}) + .noUnknown(true) + .strict(); + +const authSchema = yup.object({ + mode: yup + .string() + .oneOf(['inherit', 'none', 'awsv4', 'basic', 'bearer', 'digest', 'ntlm', 'oauth2', 'wsse', 'apikey']) + .required('mode is required'), + awsv4: authAwsV4Schema.nullable(), + basic: authBasicSchema.nullable(), + bearer: authBearerSchema.nullable(), + ntlm: authNTLMSchema.nullable(), + digest: authDigestSchema.nullable(), + oauth2: oauth2Schema.nullable(), + wsse: authWsseSchema.nullable(), + apikey: authApiKeySchema.nullable() +}) + .noUnknown(true) + .strict() + .nullable(); + +const requestParamsSchema = yup.object({ + uid: uidSchema, + name: yup.string().nullable(), + value: yup.string().nullable(), + description: yup.string().nullable(), + type: yup.string().oneOf(['query', 'path']).required('type is required'), + enabled: yup.boolean() +}) + .noUnknown(true) + .strict(); + +const exampleSchema = yup.object({ + uid: uidSchema, + itemUid: uidSchema, + name: yup.string().min(1, 'name must be at least 1 character').required('name is required'), + description: yup.string().nullable(), + type: yup + .string() + .oneOf(['http-request', 'graphql-request', 'grpc-request']) + .required('type is required'), + request: yup + .object({ + url: requestUrlSchema, + method: requestMethodSchema, + headers: yup.array().of(keyValueSchema).required('headers are required'), + params: yup.array().of(requestParamsSchema).required('params are required'), + body: requestBodySchema + }) + .noUnknown(true) + .strict() + .nullable(), + response: yup + .object({ + status: yup.string().nullable(), + statusText: yup.string().nullable(), + headers: yup.array().of(keyValueSchema).nullable(), + body: yup + .object({ + type: yup.string().oneOf(['json', 'text', 'xml', 'html', 'binary']).nullable(), + content: yup.mixed().nullable() + }) + .nullable() + }) + .noUnknown(true) + .strict() + .nullable() +}) + .noUnknown(true) + .strict(); + +export const requestSchema = yup.object({ + url: requestUrlSchema, + method: requestMethodSchema, + headers: yup.array().of(keyValueSchema).required('headers are required'), + params: yup.array().of(requestParamsSchema).required('params are required'), + auth: authSchema, + body: requestBodySchema, + script: yup + .object({ + req: yup.string().nullable(), + res: yup.string().nullable() + }) + .noUnknown(true) + .strict(), + vars: yup + .object({ + req: yup.array().of(varsSchema).nullable(), + res: yup.array().of(varsSchema).nullable() + }) + .noUnknown(true) + .strict() + .nullable(), + assertions: yup.array().of(assertionSchema).nullable(), + tests: yup.string().nullable(), + docs: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const grpcRequestSchema = yup.object({ + url: requestUrlSchema, + method: yup.string().optional(), + methodType: yup + .string() + .oneOf(['unary', 'client-streaming', 'server-streaming', 'bidi-streaming', '']) + .nullable(), + protoPath: yup.string().nullable(), + headers: yup.array().of(keyValueSchema).required('headers are required'), + auth: authSchema, + body: yup + .object({ + mode: yup.string().oneOf(['grpc']).required('mode is required'), + grpc: yup + .array() + .of( + yup.object({ + name: yup.string().nullable(), + content: yup.string().nullable() + }) + ) + .nullable() + }) + .strict() + .required('body is required'), + script: yup + .object({ + req: yup.string().nullable(), + res: yup.string().nullable() + }) + .noUnknown(true) + .strict(), + vars: yup + .object({ + req: yup.array().of(varsSchema).nullable(), + res: yup.array().of(varsSchema).nullable() + }) + .noUnknown(true) + .strict() + .nullable(), + assertions: yup.array().of(assertionSchema).nullable(), + tests: yup.string().nullable(), + docs: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const wsRequestSchema = yup.object({ + url: requestUrlSchema, + headers: yup.array().of(keyValueSchema).required('headers are required'), + auth: authSchema, + body: yup + .object({ + mode: yup.string().oneOf(['ws']).required('mode is required'), + ws: yup + .array() + .of( + yup.object({ + name: yup.string().nullable(), + type: yup.string().nullable(), + content: yup.string().nullable() + }) + ) + .nullable() + }) + .strict() + .required('body is required'), + script: yup + .object({ + req: yup.string().nullable(), + res: yup.string().nullable() + }) + .noUnknown(true) + .strict(), + vars: yup + .object({ + req: yup.array().of(varsSchema).nullable(), + res: yup.array().of(varsSchema).nullable() + }) + .noUnknown(true) + .strict() + .nullable(), + assertions: yup.array().of(assertionSchema).nullable(), + tests: yup.string().nullable(), + docs: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +const wsSettingsSchema = yup.object({ + settings: yup + .object({ + timeout: yup.number().default(500), + keepAliveInterval: yup.number().default(0) + }) + .noUnknown(true) + .strict() + .nullable() +}); + +const folderRootSchema = yup.object({ + request: yup + .object({ + headers: yup.array().of(keyValueSchema).nullable(), + auth: authSchema, + script: yup + .object({ + req: yup.string().nullable(), + res: yup.string().nullable() + }) + .noUnknown(true) + .strict() + .nullable(), + vars: yup + .object({ + req: yup.array().of(varsSchema).nullable(), + res: yup.array().of(varsSchema).nullable() + }) + .noUnknown(true) + .strict() + .nullable(), + tests: yup.string().nullable() + }) + .noUnknown(true) + .strict() + .nullable(), + docs: yup.string().nullable(), + meta: yup + .object({ + name: yup.string().nullable(), + seq: yup.number().min(1).nullable() + }) + .noUnknown(true) + .strict() + .nullable() +}) + .noUnknown(true) + .nullable(); + +export const itemSchema: yup.AnyObjectSchema = yup.object({ + uid: uidSchema, + type: yup + .string() + .oneOf(['http-request', 'graphql-request', 'folder', 'js', 'grpc-request', 'ws-request']) + .required('type is required'), + seq: yup.number().min(1), + name: yup.string().min(1, 'name must be at least 1 character').required('name is required'), + tags: yup.array().of(yup.string().matches(/^[\w-]+$/, 'tag must be alphanumeric')), + request: yup + .mixed() + .when('type', { + is: (type: unknown): type is string => type === 'grpc-request', + then: grpcRequestSchema.required('request is required when item-type is grpc-request'), + otherwise: yup + .mixed() + .when('type', { + is: (type: unknown): type is string => type === 'ws-request', + then: wsRequestSchema.required('request is required when item-type is ws-request'), + otherwise: requestSchema.when('type', { + is: (type: unknown): type is string => + ['http-request', 'graphql-request'].includes(type as string), + then: (schema) => schema.required('request is required when item-type is request') + }) + }) + }), + settings: yup + .mixed() + .when('type', { + is: (type: unknown): type is string => type === 'ws-request', + then: wsSettingsSchema, + otherwise: yup + .object({ + encodeUrl: yup.boolean().nullable(), + followRedirects: yup.boolean().nullable(), + maxRedirects: yup.number().min(0).max(50).nullable(), + timeout: yup.mixed().nullable() + }) + .noUnknown(true) + .strict() + .nullable() + }), + fileContent: yup.string().when('type', { + is: 'js', + then: yup.string(), + otherwise: yup.string().nullable() + }), + root: yup.mixed().when('type', { + is: 'folder', + then: folderRootSchema, + otherwise: yup.mixed().nullable().notRequired() + }), + items: yup.array().of( + yup.lazy(() => { + return itemSchema; + }) as unknown as yup.AnySchema + ), + examples: yup.array().of(exampleSchema).when('type', { + is: (type: unknown): type is string => + ['http-request', 'graphql-request', 'grpc-request'].includes(type as string), + then: (schema) => schema.nullable(), + otherwise: yup.array().strip() + }), + filename: yup.string().nullable(), + pathname: yup.string().nullable() +}) + .noUnknown(true) + .strict(); + +export const collectionSchema = yup.object({ + version: yup.string().oneOf(['1']).required('version is required'), + uid: uidSchema, + name: yup.string().min(1, 'name must be at least 1 character').required('name is required'), + items: yup.array().of(itemSchema), + activeEnvironmentUid: yup + .string() + .length(21, 'activeEnvironmentUid must be 21 characters in length') + .matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric') + .nullable(), + environments: environmentsSchema, + pathname: yup.string().nullable(), + runnerResult: yup.object({ + items: yup.array() + }), + runtimeVariables: yup.object(), + brunoConfig: yup.object(), + root: folderRootSchema +}) + .noUnknown(true) + .strict(); diff --git a/packages/bruno-schema/src/collections/itemSchema.spec.js b/packages/bruno-schema/src/collections/itemSchema.spec.ts similarity index 50% rename from packages/bruno-schema/src/collections/itemSchema.spec.js rename to packages/bruno-schema/src/collections/itemSchema.spec.ts index 9d52132da6..769d74cc95 100644 --- a/packages/bruno-schema/src/collections/itemSchema.spec.js +++ b/packages/bruno-schema/src/collections/itemSchema.spec.ts @@ -1,9 +1,10 @@ -const { expect } = require('@jest/globals'); -const { uuid, validationErrorWithMessages } = require('../utils/testUtils'); -const { itemSchema } = require('./index'); +import { expect } from '@jest/globals'; +import { uuid, validationErrorWithMessages } from '../utils/testUtils'; +import { itemSchema } from './index'; describe('Item Schema Validation', () => { it('item schema must validate successfully - simple items', async () => { + /** @type {{ uid: string; name: string; type: 'folder'; tags: readonly string[] }} */ const item = { uid: uuid(), name: 'A Folder', @@ -16,55 +17,51 @@ describe('Item Schema Validation', () => { }); it('item schema must throw an error if name is missing', async () => { + /** @type {{ uid: string; type: 'folder' }} */ const item = { uid: uuid(), type: 'folder' }; - return Promise.all([ - expect(itemSchema.validate(item)).rejects.toEqual(validationErrorWithMessages('name is required')) - ]); + await expect(itemSchema.validate(item)).rejects.toEqual(validationErrorWithMessages('name is required')); }); it('item schema must throw an error if name is empty', async () => { + /** @type {{ uid: string; name: string; type: 'folder' }} */ const item = { uid: uuid(), name: '', type: 'folder' }; - return Promise.all([ - expect(itemSchema.validate(item)).rejects.toEqual( - validationErrorWithMessages('name must be at least 1 character') - ) - ]); + await expect(itemSchema.validate(item)).rejects.toEqual( + validationErrorWithMessages('name must be at least 1 character') + ); }); it('item schema must throw an error if request is not present when item-type is http-request', async () => { + /** @type {{ uid: string; name: string; type: 'http-request' }} */ const item = { uid: uuid(), name: 'Get Users', type: 'http-request' }; - return Promise.all([ - expect(itemSchema.validate(item)).rejects.toEqual( - validationErrorWithMessages('request is required when item-type is request') - ) - ]); + await expect(itemSchema.validate(item)).rejects.toEqual( + validationErrorWithMessages('request is required when item-type is request') + ); }); it('item schema must throw an error if request is not present when item-type is graphql-request', async () => { + /** @type {{ uid: string; name: string; type: 'graphql-request' }} */ const item = { uid: uuid(), name: 'Get Users', type: 'graphql-request' }; - return Promise.all([ - expect(itemSchema.validate(item)).rejects.toEqual( - validationErrorWithMessages('request is required when item-type is request') - ) - ]); + await expect(itemSchema.validate(item)).rejects.toEqual( + validationErrorWithMessages('request is required when item-type is request') + ); }); }); diff --git a/packages/bruno-schema/src/collections/requestSchema.spec.js b/packages/bruno-schema/src/collections/requestSchema.spec.ts similarity index 91% rename from packages/bruno-schema/src/collections/requestSchema.spec.js rename to packages/bruno-schema/src/collections/requestSchema.spec.ts index 258b1138f3..f91ecde6ad 100644 --- a/packages/bruno-schema/src/collections/requestSchema.spec.js +++ b/packages/bruno-schema/src/collections/requestSchema.spec.ts @@ -1,6 +1,5 @@ -const { expect } = require('@jest/globals'); -const { uuid, validationErrorWithMessages } = require('../utils/testUtils'); -const { requestSchema } = require('./index'); +import { expect } from "@jest/globals"; +import { requestSchema } from './index'; describe('Request Schema Validation', () => { it('request schema must validate successfully - simple request', async () => { diff --git a/packages/bruno-schema/src/common/index.js b/packages/bruno-schema/src/common/index.ts similarity index 62% rename from packages/bruno-schema/src/common/index.js rename to packages/bruno-schema/src/common/index.ts index 9f29df8ff5..b22557288f 100644 --- a/packages/bruno-schema/src/common/index.js +++ b/packages/bruno-schema/src/common/index.ts @@ -1,11 +1,8 @@ -const Yup = require('yup'); +import * as yup from 'yup'; -const uidSchema = Yup.string() +export const uidSchema = yup + .string() .length(21, 'uid must be 21 characters in length') .matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric') .required('uid is required') .strict(); - -module.exports = { - uidSchema -}; diff --git a/packages/bruno-schema/src/index.js b/packages/bruno-schema/src/index.ts similarity index 100% rename from packages/bruno-schema/src/index.js rename to packages/bruno-schema/src/index.ts diff --git a/packages/bruno-schema/src/utils/testUtils.js b/packages/bruno-schema/src/utils/testUtils.js deleted file mode 100644 index 1c3e08c7e1..0000000000 --- a/packages/bruno-schema/src/utils/testUtils.js +++ /dev/null @@ -1,22 +0,0 @@ -const { customAlphabet } = require('nanoid'); -const { expect } = require('@jest/globals'); - -// a customized version of nanoid without using _ and - -const uuid = () => { - // https://github.com/ai/nanoid/blob/main/url-alphabet/index.js - const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict'; - const customNanoId = customAlphabet(urlAlphabet, 21); - - return customNanoId(); -}; - -const validationErrorWithMessages = (...errors) => { - return expect.objectContaining({ - errors - }); -}; - -module.exports = { - uuid, - validationErrorWithMessages -}; diff --git a/packages/bruno-schema/src/utils/testUtils.ts b/packages/bruno-schema/src/utils/testUtils.ts new file mode 100644 index 0000000000..5d99572ccc --- /dev/null +++ b/packages/bruno-schema/src/utils/testUtils.ts @@ -0,0 +1,15 @@ +import { customAlphabet } from 'nanoid'; +import { expect } from '@jest/globals'; + +const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict'; + +export const uuid = (): string => { + const customNanoId = customAlphabet(urlAlphabet, 21); + return customNanoId(); +}; + +export const validationErrorWithMessages = (...errors: string[]): ReturnType => { + return expect.objectContaining({ + errors + }); +}; diff --git a/packages/bruno-schema/tsconfig.json b/packages/bruno-schema/tsconfig.json new file mode 100644 index 0000000000..78dc4cc37d --- /dev/null +++ b/packages/bruno-schema/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "declaration": true, + "allowJs": false, + "checkJs": false, + "noImplicitAny": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "**/*.spec.ts" + ] +} \ No newline at end of file