chore: restore initial vtuber source snapshot
This commit is contained in:
2
apps/api/.env.example
Normal file
2
apps/api/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
PORT=4000
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/vtuber
|
||||
8
apps/api/.eslintrc.json
Normal file
8
apps/api/.eslintrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
|
||||
"parserOptions": {
|
||||
"project": ["./tsconfig.json"]
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"]
|
||||
}
|
||||
15
apps/api/Dockerfile
Normal file
15
apps/api/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:20
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
COPY tsconfig.base.json ./
|
||||
COPY packages ./packages
|
||||
COPY apps/api ./apps/api
|
||||
|
||||
RUN npm install
|
||||
RUN npm run db:generate -w packages/db
|
||||
RUN npm run build:api
|
||||
|
||||
ENV NODE_ENV=production
|
||||
EXPOSE 4000
|
||||
CMD ["npm","run","start","-w","apps/api"]
|
||||
8
apps/api/nest-cli.json
Normal file
8
apps/api/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"assets": ["**/*.json"],
|
||||
"watchAssets": true
|
||||
}
|
||||
}
|
||||
35
apps/api/package.json
Normal file
35
apps/api/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@vtuber/api",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"start": "node dist/main.js",
|
||||
"build": "tsc -p tsconfig.build.json",
|
||||
"start:dev": "ts-node -r tsconfig-paths/register src/main.ts",
|
||||
"lint": "eslint '{src,test}/**/*.ts' --fix",
|
||||
"test": "echo \"No tests yet\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^10.3.0",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
"@nestjs/core": "^10.3.0",
|
||||
"@nestjs/platform-express": "^10.3.0",
|
||||
"@vtuber/db": "*",
|
||||
"@prisma/client": "^5.18.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^22.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.21.0",
|
||||
"@typescript-eslint/parser": "^8.21.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"prisma": "^5.18.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.5.0"
|
||||
}
|
||||
}
|
||||
494
apps/api/src/app.controller.ts
Normal file
494
apps/api/src/app.controller.ts
Normal file
@@ -0,0 +1,494 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Patch,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
type OrderStatus = 'PENDING' | 'PAID' | 'SHIPPED' | 'CANCELLED';
|
||||
type LiveRoomStatus = 'PLANNING' | 'LIVE' | 'FINISHED' | 'CANCELLED';
|
||||
type TeamRole =
|
||||
| 'HOST'
|
||||
| 'PRODUCER'
|
||||
| 'DIRECTOR'
|
||||
| 'EDITOR'
|
||||
| 'SUPPORT'
|
||||
| 'WAREHOUSE'
|
||||
| 'MARKETING'
|
||||
| 'OPERATOR';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('health')
|
||||
health() {
|
||||
return this.appService.health();
|
||||
}
|
||||
|
||||
@Get('products')
|
||||
async listProducts() {
|
||||
return this.appService.listProducts();
|
||||
}
|
||||
|
||||
@Get('products/:id')
|
||||
async getProduct(@Param('id') id: string) {
|
||||
const product = await this.appService.getProduct(id);
|
||||
if (!product) {
|
||||
throw new BadRequestException('找不到商品');
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
@Get('live-rooms')
|
||||
async listLiveRooms() {
|
||||
return this.appService.listLiveRooms();
|
||||
}
|
||||
|
||||
@Get('live-rooms/:id/messages')
|
||||
async listLiveMessages(
|
||||
@Param('id') id: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
const parsedLimit = Number(limit ?? 30);
|
||||
const validLimit = Number.isNaN(parsedLimit) ? 30 : parsedLimit;
|
||||
return this.appService.listLiveMessages(id, validLimit);
|
||||
}
|
||||
|
||||
@Get('live-rooms/:id/operations')
|
||||
async getLiveRoomOperations(@Param('id') id: string) {
|
||||
return this.appService.getLiveRoomOperations(id);
|
||||
}
|
||||
|
||||
@Post('live-rooms/:id/messages')
|
||||
async postLiveMessage(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
body: {
|
||||
userName?: string;
|
||||
message?: string;
|
||||
productVariantId?: string;
|
||||
},
|
||||
) {
|
||||
const userName = body.userName?.trim() ?? '';
|
||||
const message = body.message?.trim() ?? '';
|
||||
if (!userName || !message) {
|
||||
throw new BadRequestException('userName 與 message 是必要欄位');
|
||||
}
|
||||
|
||||
return this.appService.postLiveMessage({
|
||||
liveRoomId: id,
|
||||
userName,
|
||||
message,
|
||||
productVariantId: body.productVariantId?.trim(),
|
||||
});
|
||||
}
|
||||
|
||||
@Get('admin/team-members')
|
||||
async listTeamMembers() {
|
||||
return this.appService.listTeamMembers();
|
||||
}
|
||||
|
||||
@Post('admin/team-members')
|
||||
async createTeamMember(
|
||||
@Body()
|
||||
body: {
|
||||
displayName?: string;
|
||||
role?: TeamRole;
|
||||
nickname?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
notes?: string;
|
||||
},
|
||||
) {
|
||||
const displayName = body.displayName?.trim();
|
||||
if (!displayName || !body.role) {
|
||||
throw new BadRequestException('displayName 與 role 是必要欄位');
|
||||
}
|
||||
|
||||
const result = await this.appService.createTeamMember({
|
||||
displayName,
|
||||
role: body.role,
|
||||
nickname: body.nickname?.trim(),
|
||||
phone: body.phone?.trim(),
|
||||
email: body.email?.trim(),
|
||||
notes: body.notes?.trim(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete('admin/team-members/:id')
|
||||
async deleteTeamMember(@Param('id') id: string) {
|
||||
return this.appService.removeTeamMember(id);
|
||||
}
|
||||
|
||||
@Get('admin/products')
|
||||
async listAdminProducts() {
|
||||
return this.appService.listAdminProducts();
|
||||
}
|
||||
|
||||
@Post('admin/products')
|
||||
async createProduct(
|
||||
@Body()
|
||||
body: {
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
imageUrl?: string;
|
||||
basePrice: number;
|
||||
liveRoomId?: string | null;
|
||||
variants?: Array<{
|
||||
sku: string;
|
||||
name: string;
|
||||
size?: string | null;
|
||||
price: number;
|
||||
stock: number;
|
||||
}>;
|
||||
},
|
||||
) {
|
||||
if (!body?.name?.trim() || !body?.slug?.trim()) {
|
||||
throw new BadRequestException('name/slug/basePrice 為必要欄位');
|
||||
}
|
||||
const basePrice = Number(body.basePrice);
|
||||
if (Number.isNaN(basePrice) || basePrice < 0) {
|
||||
throw new BadRequestException('basePrice 必須是非負數');
|
||||
}
|
||||
|
||||
return this.appService.createProduct({
|
||||
name: body.name,
|
||||
slug: body.slug,
|
||||
description: body.description,
|
||||
imageUrl: body.imageUrl,
|
||||
basePrice,
|
||||
liveRoomId: body.liveRoomId ?? null,
|
||||
variants: body.variants,
|
||||
});
|
||||
}
|
||||
|
||||
@Put('admin/products/:id')
|
||||
async updateProduct(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
body: {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
description?: string | null;
|
||||
imageUrl?: string | null;
|
||||
basePrice?: number;
|
||||
isActive?: boolean;
|
||||
liveRoomId?: string | null;
|
||||
},
|
||||
) {
|
||||
return this.appService.updateProduct(id, body);
|
||||
}
|
||||
|
||||
@Delete('admin/products/:id')
|
||||
async deleteProduct(@Param('id') id: string) {
|
||||
return this.appService.deleteProduct(id);
|
||||
}
|
||||
|
||||
@Get('admin/orders')
|
||||
async listAdminOrders() {
|
||||
return this.appService.listAdminOrders();
|
||||
}
|
||||
|
||||
@Patch('admin/orders/:id/status')
|
||||
async updateOrderStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { status: string },
|
||||
) {
|
||||
const normalizedStatus = body.status?.toUpperCase();
|
||||
if (
|
||||
!normalizedStatus ||
|
||||
!(['PENDING', 'PAID', 'SHIPPED', 'CANCELLED'] as const).includes(
|
||||
normalizedStatus as OrderStatus,
|
||||
)
|
||||
) {
|
||||
throw new BadRequestException('status 僅能為 PENDING/PAID/SHIPPED/CANCELLED');
|
||||
}
|
||||
|
||||
return this.appService.updateOrderStatus(id, normalizedStatus as OrderStatus);
|
||||
}
|
||||
|
||||
@Get('admin/inventories')
|
||||
async listInventories() {
|
||||
return this.appService.listInventories();
|
||||
}
|
||||
|
||||
@Put('admin/inventories/:variantId')
|
||||
async updateInventory(
|
||||
@Param('variantId') variantId: string,
|
||||
@Body() body: { quantity: number },
|
||||
) {
|
||||
const quantity = Number(body.quantity);
|
||||
if (Number.isNaN(quantity)) {
|
||||
throw new BadRequestException('quantity 必須為數字');
|
||||
}
|
||||
const result = await this.appService.updateInventory(variantId, quantity);
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('admin/live-rooms/:id/operations')
|
||||
async getLiveRoomOperationsAdmin(@Param('id') id: string) {
|
||||
return this.appService.getLiveRoomOperations(id);
|
||||
}
|
||||
|
||||
@Post('admin/live-rooms/:id/operations/assignments')
|
||||
async createLiveRoomAssignment(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
body: {
|
||||
teamMemberId?: string;
|
||||
role?: TeamRole;
|
||||
isPrimary?: boolean;
|
||||
shiftStartAt?: string;
|
||||
shiftEndAt?: string;
|
||||
note?: string;
|
||||
},
|
||||
) {
|
||||
if (!body.teamMemberId || !body.role) {
|
||||
throw new BadRequestException('teamMemberId 與 role 是必要欄位');
|
||||
}
|
||||
|
||||
const result = await this.appService.createLiveRoomAssignment(id, {
|
||||
teamMemberId: body.teamMemberId,
|
||||
role: body.role,
|
||||
isPrimary: !!body.isPrimary,
|
||||
shiftStartAt: body.shiftStartAt,
|
||||
shiftEndAt: body.shiftEndAt,
|
||||
note: body.note?.trim(),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete('admin/live-rooms/:id/operations/assignments/:assignmentId')
|
||||
async removeLiveRoomAssignment(
|
||||
@Param('id') liveRoomId: string,
|
||||
@Param('assignmentId') assignmentId: string,
|
||||
) {
|
||||
return this.appService.removeLiveRoomAssignment(liveRoomId, assignmentId);
|
||||
}
|
||||
|
||||
@Patch('admin/live-rooms/:id/status')
|
||||
async updateLiveRoomStatus(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { status?: LiveRoomStatus },
|
||||
) {
|
||||
if (!body.status) {
|
||||
throw new BadRequestException('status 是必要欄位');
|
||||
}
|
||||
const result = await this.appService.updateLiveRoomStatus(id, body.status);
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('admin/live-rooms/:id/operations/scripts')
|
||||
async createLiveRoomScript(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
body: {
|
||||
sequence?: number;
|
||||
cue?: string;
|
||||
title?: string;
|
||||
content?: string;
|
||||
ownerRole?: TeamRole;
|
||||
targetProductId?: string;
|
||||
},
|
||||
) {
|
||||
const payload = {
|
||||
sequence: Number(body.sequence ?? 0),
|
||||
cue: body.cue?.trim(),
|
||||
title: body.title?.trim() ?? '',
|
||||
content: body.content?.trim() ?? '',
|
||||
ownerRole: body.ownerRole,
|
||||
targetProductId: body.targetProductId?.trim(),
|
||||
};
|
||||
|
||||
if (!payload.title || !payload.content) {
|
||||
throw new BadRequestException('title/content 是必要欄位');
|
||||
}
|
||||
if (Number.isNaN(payload.sequence) || payload.sequence <= 0) {
|
||||
throw new BadRequestException('sequence 必須是正整數');
|
||||
}
|
||||
|
||||
const result = await this.appService.createLiveRoomScript(id, payload);
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Patch('admin/live-ops/scripts/:id')
|
||||
async updateLiveRoomScript(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
body: {
|
||||
sequence?: number;
|
||||
cue?: string;
|
||||
title?: string;
|
||||
content?: string;
|
||||
ownerRole?: TeamRole;
|
||||
targetProductId?: string;
|
||||
isDone?: boolean;
|
||||
},
|
||||
) {
|
||||
const result = await this.appService.updateLiveRoomScript(id, {
|
||||
sequence: body.sequence,
|
||||
cue: body.cue?.trim(),
|
||||
title: body.title?.trim(),
|
||||
content: body.content?.trim(),
|
||||
ownerRole: body.ownerRole,
|
||||
targetProductId: body.targetProductId?.trim(),
|
||||
isDone: body.isDone,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('admin/live-rooms/:id/operations/checklists')
|
||||
async createLiveRoomChecklistItem(
|
||||
@Param('id') id: string,
|
||||
@Body()
|
||||
body: {
|
||||
title?: string;
|
||||
ownerRole?: TeamRole;
|
||||
description?: string;
|
||||
isRequired?: boolean;
|
||||
note?: string;
|
||||
},
|
||||
) {
|
||||
const title = body.title?.trim() ?? '';
|
||||
const ownerRole = body.ownerRole;
|
||||
if (!title || !ownerRole) {
|
||||
throw new BadRequestException('title/ownerRole 是必要欄位');
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title,
|
||||
description: body.description?.trim(),
|
||||
isRequired: body.isRequired ?? true,
|
||||
note: body.note?.trim(),
|
||||
ownerRole,
|
||||
};
|
||||
|
||||
const result = await this.appService.createLiveRoomChecklistItem(id, payload);
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Patch('admin/live-ops/checklists/:id')
|
||||
async updateLiveRoomChecklistItem(
|
||||
@Param('id') id: string,
|
||||
@Body() body: { isDone?: boolean },
|
||||
) {
|
||||
const result = await this.appService.updateLiveRoomChecklistItem(id, {
|
||||
isDone: body.isDone,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get('cart')
|
||||
async getCart(@Query('sessionId') sessionId: string) {
|
||||
return this.appService.getCartBySession(sessionId);
|
||||
}
|
||||
|
||||
@Post('cart/items')
|
||||
async addCartItem(
|
||||
@Body()
|
||||
body: {
|
||||
sessionId?: string;
|
||||
productVariantId?: string;
|
||||
quantity?: number;
|
||||
},
|
||||
) {
|
||||
if (!body?.sessionId) {
|
||||
throw new BadRequestException('sessionId 是必要參數');
|
||||
}
|
||||
if (!body?.productVariantId) {
|
||||
throw new BadRequestException('productVariantId 是必要參數');
|
||||
}
|
||||
const quantity = Number(body.quantity ?? 1);
|
||||
if (Number.isNaN(quantity) || quantity <= 0) {
|
||||
throw new BadRequestException('quantity 需為正整數');
|
||||
}
|
||||
|
||||
const result = await this.appService.addCartItem({
|
||||
sessionId: body.sessionId,
|
||||
productVariantId: body.productVariantId,
|
||||
quantity,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete('cart/items/:id')
|
||||
async removeCartItem(@Param('id') id: string) {
|
||||
return this.appService.removeCartItem(id);
|
||||
}
|
||||
|
||||
@Post('checkout')
|
||||
async checkout(@Body() body: { sessionId?: string; liveRoomId?: string }) {
|
||||
if (!body?.sessionId) {
|
||||
throw new BadRequestException('sessionId 是必要參數');
|
||||
}
|
||||
const result = await this.appService.createMockCheckout({
|
||||
sessionId: body.sessionId,
|
||||
liveRoomId: body.liveRoomId,
|
||||
});
|
||||
if (!result.ok) {
|
||||
throw new BadRequestException(result.message);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post('ai/product-chat')
|
||||
async productChat(
|
||||
@Body()
|
||||
body: {
|
||||
userMessage?: string;
|
||||
currentProductId?: string;
|
||||
liveRoomId?: string;
|
||||
},
|
||||
) {
|
||||
if (!body?.userMessage?.trim()) {
|
||||
throw new BadRequestException('userMessage 是必要參數');
|
||||
}
|
||||
|
||||
const result = await this.appService.answerProductChat({
|
||||
userMessage: body.userMessage,
|
||||
currentProductId: body.currentProductId,
|
||||
liveRoomId: body.liveRoomId,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
12
apps/api/src/app.module.ts
Normal file
12
apps/api/src/app.module.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { DatabaseModule } from './database/database.module';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule.forRoot({ isGlobal: true }), DatabaseModule],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
1219
apps/api/src/app.service.ts
Normal file
1219
apps/api/src/app.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
9
apps/api/src/database/database.module.ts
Normal file
9
apps/api/src/database/database.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { PrismaService } from './prisma.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class DatabaseModule {}
|
||||
13
apps/api/src/database/prisma.service.ts
Normal file
13
apps/api/src/database/prisma.service.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
}
|
||||
14
apps/api/src/main.ts
Normal file
14
apps/api/src/main.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.setGlobalPrefix('api');
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 4000;
|
||||
|
||||
await app.listen(port);
|
||||
Logger.log(`VTuber API running on http://localhost:${port}/api`, 'Bootstrap');
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
4
apps/api/tsconfig.build.json
Normal file
4
apps/api/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "**/*spec.ts"]
|
||||
}
|
||||
19
apps/api/tsconfig.json
Normal file
19
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2022",
|
||||
"lib": ["es2022", "dom"],
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": false,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.js"]
|
||||
}
|
||||
Reference in New Issue
Block a user