diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..194d00f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,50 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose +{ + "name": "Existing Docker Compose (Extend)", + // Update the 'dockerComposeFile' list if you have more compose files or use different names. + // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make. + "dockerComposeFile": [ + "../docker-compose.yml", + "docker-compose.yml" + ], + // The 'service' property is the name of the service for the container that VS Code should + // use. Update this value and .devcontainer/docker-compose.yml to the real service name. + "service": "app", + // The optional 'workspaceFolder' property is the path VS Code should open by default when + // connected. This is typically a file mount in .devcontainer/docker-compose.yml + "workspaceFolder": "/usr/src/app", + "postCreateCommand": "npm install", + "customizations": { + "vscode": { + "extensions": [ + "esbenp.prettier-vscode", + "editorconfig.editorconfig", + "dbaeumer.vscode-eslint", + "wayou.vscode-todo-highlight", + "mike-co.import-sorter", + "waderyan.gitblame", + "ms-vscode.vscode-typescript-tslint-plugin", + "ms-azuretools.vscode-docker" + ] + } + }, + "mounts": [ + "source=${localWorkspaceFolder},target=/usr/src/app,type=bind", + "source=node_modules,target=/usr/src/app/node_modules,type=volume" + ], + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + // Uncomment the next line if you want start specific services in your Docker Compose config. + // "runServices": [], + // Uncomment the next line if you want to keep your containers running after VS Code shuts down. + // "shutdownAction": "none", + // Uncomment the next line to run commands after the container is created. + // "postCreateCommand": "cat /etc/os-release", + // Configure tool-specific properties. + // "customizations": {}, + // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "vscode" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..72a9b99 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.8' +services: + # Update this to the name of the service you want to work with in your docker-compose.yml file + app: + # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer + # folder. Note that the path of the Dockerfile and context is relative to the *primary* + # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile" + # array). The sample below assumes your primary file is in the root of your project. + # + # build: + # context: . + # dockerfile: .devcontainer/Dockerfile + + volumes: + # Update this to wherever you want VS Code to mount the folder of your project + - ..:/usr/src/app:cached + - node_modules:/usr/src/app/node_modules + + # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust. + # cap_add: + # - SYS_PTRACE + # security_opt: + # - seccomp:unconfined + + # Overrides default command so things don't shut down after the process ends. + command: /bin/sh -c "while sleep 1000; do :; done" + +volumes: + node_modules: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f34371b..d31443c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,8 @@ -# Basic dependabot.yml file with -# minimum configuration for two package managers +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot version: 2 updates: @@ -9,7 +12,7 @@ updates: directory: '/' # Check the npm registry for updates every day (weekdays) schedule: - interval: 'daily' + interval: 'weekly' # Enable version updates for Docker - package-ecosystem: 'docker' @@ -18,3 +21,9 @@ updates: # Check for updates once a week schedule: interval: 'weekly' + + # Enable version updates for devcontainers + - package-ecosystem: 'devcontainers' + directory: '/' + schedule: + interval: 'weekly' diff --git a/.gitignore b/.gitignore index f9f57c5..70517ba 100644 --- a/.gitignore +++ b/.gitignore @@ -392,3 +392,4 @@ dist local/ documentation test-report.xml +tsconfig.build.tsbuildinfo diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..02fda06 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 20.14.0 diff --git a/.vscode/settings.json b/.vscode/settings.json index f2920a8..c936691 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "editor.codeActionsOnSave": { - "source.fixAll.tslint": true + "source.fixAll.tslint": "explicit" }, "editor.insertSpaces": true, "editor.tabSize": 2, diff --git a/Dockerfile b/Dockerfile index 90177dd..a88116c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,30 @@ -# lts-gallium refers to v16 -# Using this instead of node:16 to avoid dependabot updates -FROM node:lts-gallium as builder +# Development Dockerfile +FROM node:20.14.0-bookworm-slim + +# Install additional tools for development +RUN apt-get update && apt-get install -y \ + vim \ + curl \ + wget \ + git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app +# Copy and install dependencies COPY package.json package-lock.json ./ -RUN npm ci +RUN npm install +# Copy the rest of the application files COPY . . +# Set the environment variables ARG APP_ENV=development ENV NODE_ENV=${APP_ENV} -RUN npm run build - -RUN npm prune - -FROM node:lts-gallium - -ARG APP_ENV=development -ENV NODE_ENV=${APP_ENV} - -WORKDIR /usr/src/app -COPY --from=builder /usr/src/app/node_modules ./node_modules -COPY --from=builder /usr/src/app/package*.json ./ -COPY --from=builder /usr/src/app/dist ./dist - +# Expose the application port EXPOSE 3000 -USER node -CMD [ "npm", "run", "start:prod" ] +# Set the default command to run the application with nodemon +CMD ["npm", "start:dev"] diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..d81d96b --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,42 @@ +# Stage 1: Build the application +FROM node:20.14.0-bookworm-slim as builder + +WORKDIR /usr/src/app + +# Install dependencies +COPY package.json package-lock.json ./ +RUN npm ci + +# Copy the rest of the application files +COPY . . + +# Build the application +ARG APP_ENV=production +ENV NODE_ENV=${APP_ENV} +RUN npm run build + +# Prune development dependencies +RUN npm prune --production + +# Stage 2: Create the production image +FROM node:20.14.0-bookworm-slim + +WORKDIR /usr/src/app + +# Copy only the necessary files from the builder stage +COPY --from=builder /usr/src/app/node_modules ./node_modules +COPY --from=builder /usr/src/app/package*.json ./ +COPY --from=builder /usr/src/app/dist ./dist + +# Set the environment variables +ARG APP_ENV=production +ENV NODE_ENV=${APP_ENV} + +# Expose the application port +EXPOSE 3000 + +# Use a non-root user for security +USER node + +# Set the default command to run the application +CMD [ "npm", "run", "start:prod" ] diff --git a/README.md b/README.md index 100be24..ed6ba5a 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,10 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) ![Build Badge](https://github.com/monstar-lab-oss/nestjs-starter-rest-api/workflows/build/badge.svg) ![Tests Badge](https://github.com/monstar-lab-oss/nestjs-starter-rest-api/workflows/tests/badge.svg) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=monstar-lab-oss_nestjs-starter-rest-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=monstar-lab-oss_nestjs-starter-rest-api) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=monstar-lab-oss_nestjs-starter-rest-api&metric=coverage)](https://sonarcloud.io/dashboard?id=monstar-lab-oss_nestjs-starter-rest-api) -[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=monstar-lab-oss_nestjs-starter-rest-api&metric=code_smells)](https://sonarcloud.io/dashboard?id=monstar-lab-oss_nestjs-starter-rest-api) This starter kit has the following outline: -- Monolithic Project. +- Monolithic Project - REST API This is a Github Template Repository, so it can be easily [used as a starter template](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) for other repositories. @@ -32,6 +29,7 @@ One of our main principals has been to keep the starter kit as lightweight as po | Request Validation | class-validator | Done | | Pagination | SQL offset & limit | Done | | Docker Ready | Dockerfile | Done | +| Devcontainer | - | Done | | Auto-generated OpenAPI | - | Done | | Auto-generated ChangeLog | - | WIP | diff --git a/docker-compose.yml b/docker-compose.yml index 065d4bb..4a87c3b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,8 +10,8 @@ services: ports: - 3000:3000 volumes: - - ./:/usr/src/app - - node_modules:/usr/src/app/node_modules/ + - ./:/usr/src/app:cached + - node_modules:/usr/src/app/node_modules environment: APP_ENV: ${APP_ENV} APP_PORT: ${APP_PORT} @@ -29,7 +29,7 @@ services: - pgsqldb pgsqldb: - image: postgres:14.3 + image: postgres:16.3 environment: POSTGRES_USER: "${DB_USER}" POSTGRES_PASSWORD: "${DB_PASS}" diff --git a/ormconfig.ts b/ormconfig.ts index f883c69..95c495d 100644 --- a/ormconfig.ts +++ b/ormconfig.ts @@ -6,7 +6,7 @@ dotenv.config(); const typeOrmConfig = new DataSource({ type: 'postgres', host: process.env.DB_HOST, - port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : null, + port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : undefined, database: process.env.DB_NAME, username: process.env.DB_USER, password: process.env.DB_PASS, diff --git a/src/article/services/article.service.ts b/src/article/services/article.service.ts index 883c0ca..d27dbe4 100644 --- a/src/article/services/article.service.ts +++ b/src/article/services/article.service.ts @@ -35,7 +35,7 @@ export class ArticleService { const article = plainToClass(Article, input); - const actor: Actor = ctx.user; + const actor: Actor = ctx.user!; const user = await this.userService.getUserById(ctx, actor.id); @@ -63,7 +63,7 @@ export class ArticleService { ): Promise<{ articles: ArticleOutput[]; count: number }> { this.logger.log(ctx, `${this.getArticles.name} was called`); - const actor: Actor = ctx.user; + const actor: Actor = ctx.user!; const isAllowed = this.aclService.forActor(actor).canDoAction(Action.List); if (!isAllowed) { @@ -90,7 +90,7 @@ export class ArticleService { ): Promise { this.logger.log(ctx, `${this.getArticleById.name} was called`); - const actor: Actor = ctx.user; + const actor: Actor = ctx.user!; this.logger.log(ctx, `calling ${ArticleRepository.name}.getById`); const article = await this.repository.getById(id); @@ -117,7 +117,7 @@ export class ArticleService { this.logger.log(ctx, `calling ${ArticleRepository.name}.getById`); const article = await this.repository.getById(articleId); - const actor: Actor = ctx.user; + const actor: Actor = ctx.user!; const isAllowed = this.aclService .forActor(actor) @@ -145,7 +145,7 @@ export class ArticleService { this.logger.log(ctx, `calling ${ArticleRepository.name}.getById`); const article = await this.repository.getById(id); - const actor: Actor = ctx.user; + const actor: Actor = ctx.user!; const isAllowed = this.aclService .forActor(actor) diff --git a/src/auth/guards/jwt-auth.guard.ts b/src/auth/guards/jwt-auth.guard.ts index c3f18fe..aa59c05 100644 --- a/src/auth/guards/jwt-auth.guard.ts +++ b/src/auth/guards/jwt-auth.guard.ts @@ -19,7 +19,7 @@ export class JwtAuthGuard extends AuthGuard(STRATEGY_JWT_AUTH) { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - handleRequest(err, user, info) { + handleRequest(err: any, user: any, info: any) { // You can throw an exception based on either "info" or "err" arguments if (err || !user) { throw err || new UnauthorizedException(`${info}`); diff --git a/src/auth/guards/jwt-refresh.guard.ts b/src/auth/guards/jwt-refresh.guard.ts index 1f4ba57..494021c 100644 --- a/src/auth/guards/jwt-refresh.guard.ts +++ b/src/auth/guards/jwt-refresh.guard.ts @@ -19,7 +19,7 @@ export class JwtRefreshGuard extends AuthGuard(STRATEGY_JWT_REFRESH) { } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - handleRequest(err, user, info) { + handleRequest(err: any, user: any, info: any) { // You can throw an exception based on either "info" or "err" arguments if (err || !user) { throw err || new UnauthorizedException(`${info}`); diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index e752687..92832e7 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -51,7 +51,7 @@ export class AuthService { login(ctx: RequestContext): AuthTokenOutput { this.logger.log(ctx, `${this.login.name} was called`); - return this.getAuthToken(ctx, ctx.user); + return this.getAuthToken(ctx, ctx.user!); } async register( @@ -73,7 +73,7 @@ export class AuthService { async refreshToken(ctx: RequestContext): Promise { this.logger.log(ctx, `${this.refreshToken.name} was called`); - const user = await this.userService.findById(ctx, ctx.user.id); + const user = await this.userService.findById(ctx, ctx.user!.id); if (!user) { throw new UnauthorizedException('Invalid user id'); } diff --git a/src/cli.ts b/src/cli.ts index da6e4f9..5f8efdc 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,7 +13,7 @@ async function bootstrap() { const configService = app.get(ConfigService); const defaultAdminUserPassword = configService.get( 'defaultAdminUserPassword', - ); + )!; const userService = app.get(UserService); diff --git a/src/main.ts b/src/main.ts index bf630cd..c53b6f6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,7 @@ import { ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; -import { DocumentBuilder,SwaggerModule } from '@nestjs/swagger'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; import { VALIDATION_PIPE_OPTIONS } from './shared/constants'; @@ -28,6 +28,6 @@ async function bootstrap() { const configService = app.get(ConfigService); const port = configService.get('port'); - await app.listen(port); + await app.listen(port || 3000); } bootstrap(); diff --git a/src/shared/acl/acl.service.ts b/src/shared/acl/acl.service.ts index 5ec6c18..b5d2c13 100644 --- a/src/shared/acl/acl.service.ts +++ b/src/shared/acl/acl.service.ts @@ -50,9 +50,16 @@ export class BaseAclService { aclRule.actions.includes(Action.Manage); //check for custom `ruleCallback` callback - canDoAction = - hasActionPermission && - (!aclRule.ruleCallback || aclRule.ruleCallback(resource, actor)); + if (!aclRule.ruleCallback) { + canDoAction = hasActionPermission; + } else { + if (!resource) { + throw new Error('Resource is required for ruleCallback'); + } + + canDoAction = + hasActionPermission && aclRule.ruleCallback(resource, actor); + } }); }); diff --git a/src/shared/configs/configuration.ts b/src/shared/configs/configuration.ts index 878a1f3..bfc0885 100644 --- a/src/shared/configs/configuration.ts +++ b/src/shared/configs/configuration.ts @@ -10,19 +10,19 @@ export default (): any => ({ }, jwt: { publicKey: Buffer.from( - process.env.JWT_PUBLIC_KEY_BASE64, + process.env.JWT_PUBLIC_KEY_BASE64!, 'base64', ).toString('utf8'), privateKey: Buffer.from( - process.env.JWT_PRIVATE_KEY_BASE64, + process.env.JWT_PRIVATE_KEY_BASE64!, 'base64', ).toString('utf8'), accessTokenExpiresInSec: parseInt( - process.env.JWT_ACCESS_TOKEN_EXP_IN_SEC, + process.env.JWT_ACCESS_TOKEN_EXP_IN_SEC!, 10, ), refreshTokenExpiresInSec: parseInt( - process.env.JWT_REFRESH_TOKEN_EXP_IN_SEC, + process.env.JWT_REFRESH_TOKEN_EXP_IN_SEC!, 10, ), }, diff --git a/src/shared/dtos/base-api-response.dto.ts b/src/shared/dtos/base-api-response.dto.ts index 05850c3..de177ed 100644 --- a/src/shared/dtos/base-api-response.dto.ts +++ b/src/shared/dtos/base-api-response.dto.ts @@ -1,3 +1,4 @@ +import { Type } from '@nestjs/common'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class BaseApiResponse { @@ -7,10 +8,19 @@ export class BaseApiResponse { public meta: any; } -export function SwaggerBaseApiResponse(type: T): typeof BaseApiResponse { +type ApiPropertyType = + | string + | Record + | Type + | [new (...args: any[]) => any] + | undefined; + +export function SwaggerBaseApiResponse( + type: T, +): typeof BaseApiResponse { class ExtendedBaseApiResponse extends BaseApiResponse { - @ApiProperty({ type }) - public data: T; + @ApiProperty({ type }) // Casting `type` to `any` to bypass type checking for now + public declare data: T; } // NOTE : Overwrite the returned class name, otherwise whichever type calls this function in the last, // will overwrite all previous definitions. i.e., Swagger will have all response types as the same one. diff --git a/src/shared/exceptions/base-api.exception.ts b/src/shared/exceptions/base-api.exception.ts index 6194a2b..470b629 100644 --- a/src/shared/exceptions/base-api.exception.ts +++ b/src/shared/exceptions/base-api.exception.ts @@ -1,8 +1,8 @@ import { HttpException } from '@nestjs/common'; export class BaseApiException extends HttpException { - public localizedMessage: Record; - public details: string | Record; + public localizedMessage: Record | undefined; + public details: string | Record | undefined; constructor( message: string, diff --git a/src/shared/filters/all-exceptions.filter.ts b/src/shared/filters/all-exceptions.filter.ts index cf8175f..756eb4d 100644 --- a/src/shared/filters/all-exceptions.filter.ts +++ b/src/shared/filters/all-exceptions.filter.ts @@ -34,20 +34,22 @@ export class AllExceptionsFilter implements ExceptionFilter { const requestContext = createRequestContext(req); let stack: any; - let statusCode: HttpStatus; - let errorName: string; - let message: string; - let details: string | Record; + let statusCode: HttpStatus | undefined = undefined; + let errorName: string | undefined = undefined; + let message: string | undefined = undefined; + let details: string | Record | undefined = undefined; // TODO : Based on language value in header, return a localized message. const acceptedLanguage = 'ja'; - let localizedMessage: string; + let localizedMessage: string | undefined = undefined; // TODO : Refactor the below cases into a switch case and tidy up error response creation. if (exception instanceof BaseApiException) { statusCode = exception.getStatus(); errorName = exception.constructor.name; message = exception.message; - localizedMessage = exception.localizedMessage[acceptedLanguage]; + localizedMessage = exception.localizedMessage + ? exception.localizedMessage[acceptedLanguage] + : undefined; details = exception.details || exception.getResponse(); } else if (exception instanceof HttpException) { statusCode = exception.getStatus(); diff --git a/src/shared/middlewares/request-id/request-id.middleware.ts b/src/shared/middlewares/request-id/request-id.middleware.ts index 62f783b..f31c1d2 100644 --- a/src/shared/middlewares/request-id/request-id.middleware.ts +++ b/src/shared/middlewares/request-id/request-id.middleware.ts @@ -9,10 +9,9 @@ export const RequestIdMiddleware = ( next: () => void, ): void => { /** set request id, if not being set yet */ - if ( - !req.headers[REQUEST_ID_TOKEN_HEADER] || - !validate(req.header(REQUEST_ID_TOKEN_HEADER)) - ) { + const requestIdToken = req.header(REQUEST_ID_TOKEN_HEADER) || ''; + + if (!requestIdToken || !validate(requestIdToken)) { req.headers[REQUEST_ID_TOKEN_HEADER] = uuidv4(); } diff --git a/src/shared/request-context/request-context.dto.ts b/src/shared/request-context/request-context.dto.ts index 7e0981d..4b45385 100644 --- a/src/shared/request-context/request-context.dto.ts +++ b/src/shared/request-context/request-context.dto.ts @@ -1,12 +1,12 @@ import { UserAccessTokenClaims } from '../../auth/dtos/auth-token-output.dto'; export class RequestContext { - public requestID: string; + public requestID: string | undefined; public url: string; - public ip: string; + public ip: string | undefined; // TODO : Discuss with team if this import is acceptable or if we should move UserAccessTokenClaims to shared. - public user: UserAccessTokenClaims; + public user: UserAccessTokenClaims | null; } diff --git a/src/user/controllers/user.controller.ts b/src/user/controllers/user.controller.ts index d40a12f..016d886 100644 --- a/src/user/controllers/user.controller.ts +++ b/src/user/controllers/user.controller.ts @@ -1,15 +1,30 @@ import { - Body, ClassSerializerInterceptor, Controller, Get, HttpStatus, Param, Patch, Query, UseGuards, - UseInterceptors + Body, + ClassSerializerInterceptor, + Controller, + Get, + HttpStatus, + Param, + Patch, + Query, + UseGuards, + UseInterceptors, } from '@nestjs/common'; -import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiOperation, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { ROLE } from '../../auth/constants/role.constant'; import { Roles } from '../../auth/decorators/role.decorator'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../../auth/guards/roles.guard'; import { - BaseApiErrorResponse, BaseApiResponse, SwaggerBaseApiResponse + BaseApiErrorResponse, + BaseApiResponse, + SwaggerBaseApiResponse, } from '../../shared/dtos/base-api-response.dto'; import { PaginationParamsDto } from '../../shared/dtos/pagination-params.dto'; import { AppLogger } from '../../shared/logger/logger.service'; @@ -28,6 +43,7 @@ export class UserController { ) { this.logger.setContext(UserController.name); } + @UseGuards(JwtAuthGuard) @ApiBearerAuth() @UseInterceptors(ClassSerializerInterceptor) @@ -48,7 +64,7 @@ export class UserController { ): Promise> { this.logger.log(ctx, `${this.getMyProfile.name} was called`); - const user = await this.userService.findById(ctx, ctx.user.id); + const user = await this.userService.findById(ctx, ctx.user!.id); return { data: user, meta: {} }; } diff --git a/tsconfig.json b/tsconfig.json index bf10a23..1cc994b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,37 @@ { "compilerOptions": { - "module": "commonjs", + "target": "ES2022", + "module": "CommonJS", + "lib": [ + "ES2022" + ], + "baseUrl": "./", + "outDir": "./dist", + "strict": true, + "strictPropertyInitialization": false, + "moduleResolution": "node", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "noImplicitAny": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "inlineSourceMap": false, + "inlineSources": true, "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", "incremental": true - } + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.spec.ts", + "node_modules", + "dist" + ] }