Commit d57d031a authored by nanahira's avatar nanahira

first

parents
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/data
/output
/config.yaml
.git*
Dockerfile
.dockerignore
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
/data
/output
/config.yaml
\ No newline at end of file
stages:
- build
- deploy
variables:
GIT_DEPTH: "1"
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
.build-image:
stage: build
script:
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
build-x86:
extends: .build-image
tags:
- docker
variables:
TARGET_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
build-arm:
extends: .build-image
tags:
- docker-arm
variables:
TARGET_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
.deploy:
stage: deploy
tags:
- docker
script:
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
- docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
- docker manifest create $TARGET_IMAGE --amend $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86 --amend
$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
- docker manifest push $TARGET_IMAGE
deploy_latest:
extends: .deploy
variables:
TARGET_IMAGE: $CI_REGISTRY_IMAGE:latest
only:
- master
deploy_branch:
extends: .deploy
variables:
TARGET_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
/install-npm.sh
.git*
/data
/output
/config.yaml
.idea
.dockerignore
Dockerfile
/src
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
FROM node:lts-trixie-slim as base
LABEL Author="Nanahira <nanahira@momobako.com>"
RUN apt update && apt -y install python3 build-essential && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /var/log/*
WORKDIR /usr/src/app
COPY ./package*.json ./
FROM base as builder
RUN npm ci && npm cache clean --force
COPY . ./
RUN npm run build
FROM base
ENV NODE_ENV production
RUN npm ci && npm cache clean --force
COPY --from=builder /usr/src/app/dist ./dist
COPY ./resources/ ./resources
COPY ./config.example.yaml ./config.yaml
EXPOSE 3000
CMD [ "npm", "run", "start:prod" ]
This diff is collapsed.
# App name
App description.
## Installation
```bash
$ npm install
```
## Config
Make a copy of `config.example.yaml` to `config.yaml`.
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## License
AGPLv3
host: '::'
port: 3000
REDIS_URL: ''
\ No newline at end of file
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn'
},
},
);
\ No newline at end of file
#!/bin/bash
npm install --save \
class-validator \
class-transformer \
@nestjs/swagger \
@nestjs/config \
yaml \
nesties
npm install --save-dev \
@types/express
# npm i --save-exact --save-dev eslint@8.22.0
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
This diff is collapsed.
{
"name": "ygopro-deckform-filler",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/axios": "^4.0.1",
"@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/swagger": "^11.2.1",
"@pdf-lib/fontkit": "^1.1.1",
"aragami": "^1.2.7",
"better-lock": "^3.2.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"nesties": "^1.1.2",
"nestjs-aragami": "^1.2.0",
"pdf-lib": "^1.17.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"yaml": "^2.8.1",
"ygopro-deck-encode": "^1.0.14"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@swc/cli": "^0.6.0",
"@swc/core": "^1.10.7",
"@types/express": "^5.0.3",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.20",
"@types/multer": "^2.0.0",
"@types/node": "^22.10.7",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"express": "^5.1.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should be defined', () => {
expect(appController).toBeDefined();
});
});
});
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { AppService } from './app.service';
import {
ApiBody,
ApiConsumes,
ApiOkResponse,
ApiProduces,
} from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
import { FileUploadDto } from './dto/file-upload.dto';
import YGOProDeck from 'ygopro-deck-encode';
import { DataQuery } from 'nesties';
import { FillOptionsDto } from './dto/fill-options.dto';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('fill')
@ApiProduces('application/pdf')
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiBody({
type: FileUploadDto,
})
@ApiOkResponse({
schema: {
type: 'string',
format: 'binary',
},
})
async fillDeckForm(
@UploadedFile() file: Express.Multer.File,
@DataQuery() dto: FillOptionsDto,
) {
const ydk = YGOProDeck.fromYdkString(file.buffer.toString('utf-8'));
return this.appService.fillDeckForm(ydk, dto);
}
}
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { loadConfig } from './utility/config';
import { AragamiModule } from 'nestjs-aragami';
import { HttpModule } from '@nestjs/axios';
import { CardDataService } from './card-data/card-data.service';
import { DrawTextService } from './draw-text/draw-text.service';
@Module({
imports: [
ConfigModule.forRoot({
load: [loadConfig],
isGlobal: true,
ignoreEnvVars: true,
ignoreEnvFile: true,
}),
HttpModule,
AragamiModule.registerAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => {
const redisUrl = config.get<string>('REDIS_URL');
if (redisUrl) {
return {
redis: {
uri: redisUrl,
},
};
}
return {};
},
}),
],
controllers: [AppController],
providers: [AppService, CardDataService, DrawTextService],
})
export class AppModule {}
import { Injectable, ConsoleLogger, StreamableFile } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import YGOProDeck from 'ygopro-deck-encode';
import { FillOptionsDto } from './dto/fill-options.dto';
import { PDFDocument } from 'pdf-lib';
import * as fs from 'node:fs';
import { DrawTextService } from './draw-text/draw-text.service';
import { Coordinates } from './draw-text/coordinates';
import moment from 'moment';
import {
DrawTextOptions,
moveDown,
moveRight,
} from './draw-text/draw-text-options';
import { CardDataService } from './card-data/card-data.service';
import _ from 'lodash';
@Injectable()
export class AppService extends ConsoleLogger {
constructor(
private readonly config: ConfigService,
private drawTextService: DrawTextService,
private cardDataService: CardDataService,
) {
super('app');
}
async fillDeckForm(ydk: YGOProDeck, dto: FillOptionsDto) {
const doc = await PDFDocument.load(
await fs.promises.readFile('./resources/deck_cn.pdf'),
);
const directDrawFields = ['name', 'event', 'lastInitial'] as const;
for (const field of directDrawFields) {
if (!dto[field]) continue;
await this.drawTextService.drawTextInBox(
doc,
dto[field],
Coordinates[field],
);
}
if (dto.date) {
const date = moment(dto.date);
// we need a dd-mm-yy format
const dateStr = date.format('DDMMYYYY');
const dateChars = dateStr.split('');
// dd-mm-yy
const datePrintStr = [
dateChars[0],
dateChars[1],
'-',
dateChars[2],
dateChars[3],
'-',
dateChars[6],
dateChars[7],
];
for (let i = 0; i < datePrintStr.length; i++) {
await this.drawTextService.drawTextInBox(
doc,
datePrintStr[i],
moveRight(Coordinates.dateFirst, i),
);
}
}
const totalCounts = [ydk.main.length, ydk.extra.length, ydk.side.length];
for (let i = 0; i < 3; ++i) {
await this.drawTextService.drawTextInBox(
doc,
totalCounts[i].toString(),
moveRight(Coordinates.totalFirst, i),
);
}
const copyFirstPageToBottom = async () => {
const [newPage] = await doc.copyPages(doc, [0]);
doc.addPage(newPage);
};
await copyFirstPageToBottom();
let pageCount = 0;
const ensurePages = async (pc: number) => {
if (pageCount >= pc) return;
const addCount = pc - pageCount;
for (let i = 0; i < addCount; ++i) {
await copyFirstPageToBottom();
}
pageCount = pc;
};
const cards = [...new Set([...ydk.main, ...ydk.extra, ...ydk.side])];
const cardDatas = await this.cardDataService.getCardDatas(cards);
const cardDataMap = _.keyBy(cardDatas, 'id');
const drawCards = async (
cards: number[],
cord: {
qty: DrawTextOptions;
name: DrawTextOptions;
},
spaces = 20,
) => {
const groupedCards = Object.values(_.groupBy(cards, (id) => id));
const usePages = _.chunk(groupedCards, spaces);
await ensurePages(usePages.length - 1);
for (let i = 0; i < usePages.length; ++i) {
const currentPageCards = usePages[i];
for (let j = 0; j < currentPageCards.length; ++j) {
const entry = currentPageCards[j];
const qty = entry.length;
const cardData = cardDataMap[entry[0]];
await this.drawTextService.drawTextInBox(
doc,
qty.toString(),
moveDown(cord.qty, j),
i + 1,
);
await this.drawTextService.drawTextInBox(
doc,
cardData.name,
moveDown(cord.name, j),
i + 1,
);
}
await this.drawTextService.drawTextInBox(
doc,
_.sumBy(currentPageCards, (s) => s.length).toString(),
moveDown(cord.qty, spaces),
i + 1,
);
}
};
const filterType = (type: number) =>
ydk.main.filter((id) => cardDataMap[id].type === type);
await drawCards(filterType(1), Coordinates.main.monsters);
await drawCards(filterType(2), Coordinates.main.spells);
await drawCards(filterType(4), Coordinates.main.traps);
await drawCards(ydk.extra, Coordinates.extra, 15);
await drawCards(ydk.side, Coordinates.side, 15);
doc.removePage(0);
return new StreamableFile(Buffer.from(await doc.save()), {
type: 'application/pdf',
disposition: `attachment; filename="deck_${dto.name || Date.now()}.pdf"`,
});
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { CardDataService } from './card-data.service';
describe('CardDataService', () => {
let service: CardDataService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CardDataService],
}).compile();
service = module.get<CardDataService>(CardDataService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { ConsoleLogger, Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { InjectAragami } from 'nestjs-aragami';
import { AragamiService } from 'nestjs-aragami/dist/src/aragami';
import { CacheKey, CacheTTL } from 'aragami';
import { lastValueFrom } from 'rxjs';
import BetterLock from 'better-lock';
import { GenericReturnMessageDto } from 'nesties';
@CacheTTL(86400 * 30)
export class CardData {
@CacheKey()
cacheKey() {
return this.id.toString();
}
id: number;
name: string;
type: number; // 0x1 for monster, 0x2 for spell, 0x4 for trap
}
@Injectable()
export class CardDataService {
constructor(
private http: HttpService,
@InjectAragami() private aragami: AragamiService,
) {}
private logger = new ConsoleLogger('CardDataService');
private async fetchCardData(id: number): Promise<CardData> {
const data = new CardData();
data.id = id;
this.logger.log(`Fetching card data for ID ${id}`);
const { data: resp, status } = await lastValueFrom(
this.http.get<{
sc_name: string;
data: { type: number };
}>('https://ygocdb.com/api/v0/card/' + id, {
timeout: 30000,
params: {
show: 'all',
},
validateStatus: (status) => status === 200 || status === 404,
}),
);
if (status === 404) {
this.logger.warn(`Card ID ${id} not found`);
throw new GenericReturnMessageDto(
404,
`Card ID ${id} not found`,
).toException();
}
data.name = resp.sc_name;
data.type = resp.data.type & 0x7;
this.logger.log(
`Fetched card data for ID ${id}: ${data.type} ${data.name}`,
);
return data;
}
private cardIdLock = new BetterLock();
async getCardData(id: number): Promise<CardData> {
return this.cardIdLock.acquire(id.toString(), () =>
this.aragami.cache(CardData, id.toString(), () => this.fetchCardData(id)),
);
}
async getCardDatas(ids: number[]): Promise<CardData[]> {
if (!ids?.length) return [];
// 1) 先对 id 去重,避免重复请求
const uniqueIds = Array.from(new Set(ids));
// 2) 并发获取,每个唯一 id 只请求一次(内部仍会命中 aragami.cache)
const uniqueResults = await Promise.all(
uniqueIds.map((id) => this.getCardData(id)),
);
// 3) 建一个 Map 方便按原顺序回填
const byId = new Map<number, CardData>();
uniqueIds.forEach((id, i) => byId.set(id, uniqueResults[i]));
// 4) 保持与入参同样的顺序与重复度返回
return ids.map((id) => byId.get(id));
}
}
export const Coordinates = {
// 顶部(含轻微内缩,避免贴边)
name: { x: 377, y: 1986, width: 480, height: 54, fontSize: 31 },
event: { x: 1107, y: 1986, width: 359, height: 54, fontSize: 31 },
lastInitial: { x: 1286, y: 2057, width: 183, height: 58, fontSize: 33 },
// 日期:只给第一格;其余你自己 x += 59 平移
dateFirst: { x: 375, y: 1905, width: 60, height: 56, fontSize: 32 },
// —— 主卡首行(Monsters / Spells 保持原对齐;Trap 全列“下移 6pt”)——
main: {
monsters: {
qty: { x: 191, y: 1800, width: 61, height: 39, fontSize: 22 },
name: { x: 251, y: 1800, width: 366, height: 39, fontSize: 23 },
},
spells: {
qty: { x: 617, y: 1800, width: 61, height: 39, fontSize: 23 },
name: { x: 677, y: 1800, width: 366, height: 39, fontSize: 23 },
},
traps: {
// ↓↓↓ 仅此列整体下移 6pt,修正“偏上”问题
qty: { x: 1043, y: 1800, width: 61, height: 39, fontSize: 23 },
name: { x: 1103, y: 1800, width: 366, height: 39, fontSize: 23 },
},
},
// 额外 / 副卡(首行)
extra: {
qty: { x: 191, y: 913, width: 61, height: 39, fontSize: 22 },
name: { x: 251, y: 913, width: 366, height: 39, fontSize: 22 },
},
side: {
qty: { x: 617, y: 913, width: 61, height: 39, fontSize: 22 },
name: { x: 677, y: 913, width: 366, height: 39, fontSize: 22 },
},
// 只保留第一个 Total(Main Total)
totalFirst: { x: 1106, y: 913, width: 122, height: 39, fontSize: 22 },
} as const;
export interface DrawTextOptions {
x: number;
y: number;
width: number;
height: number;
fontSize: number;
}
export function moveDown(o: DrawTextOptions, n = 1) {
return {
...o,
y: o.y - o.height * n,
};
}
export function moveRight(o: DrawTextOptions, n = 1) {
return {
...o,
x: o.x + o.width * n,
};
}
import { Test, TestingModule } from '@nestjs/testing';
import { DrawTextService } from './draw-text.service';
describe('DrawTextService', () => {
let service: DrawTextService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DrawTextService],
}).compile();
service = module.get<DrawTextService>(DrawTextService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable } from '@nestjs/common';
import * as fs from 'node:fs';
import { PDFDocument, PDFFont, rgb } from 'pdf-lib';
import { DrawTextOptions } from './draw-text-options';
import fontkit from '@pdf-lib/fontkit';
@Injectable()
export class DrawTextService {
private fontCache = new WeakMap<PDFDocument, PDFFont>();
async getFont(doc: PDFDocument): Promise<PDFFont> {
const cached = this.fontCache.get(doc);
if (cached) return cached;
const fontBuffer = await fs.promises.readFile('./resources/textFont.ttf');
doc.registerFontkit(fontkit);
const font = await doc.embedFont(fontBuffer);
this.fontCache.set(doc, font);
return font;
}
/**
* 在指定矩形内绘制文本(自动居中、缩放、截断)
*/
async drawTextInBox(
doc: PDFDocument,
text: string,
options: DrawTextOptions,
pageCount = 0,
) {
const { x, y, width, height, fontSize } = options;
const page = doc.getPage(pageCount);
const font = await this.getFont(doc);
let size = fontSize;
// 测量宽度并调整大小防止越界
let textWidth = font.widthOfTextAtSize(text, size);
if (textWidth > width) {
size = (width / textWidth) * size; // 缩小字体比例
textWidth = font.widthOfTextAtSize(text, size);
}
// 如果依然超出高度,继续缩小
const textHeight = font.heightAtSize(size);
if (textHeight > height) {
size = (height / textHeight) * size;
}
// 截断逻辑(若再缩仍太长,则截断)
let truncated = text;
while (
font.widthOfTextAtSize(truncated, size) > width &&
truncated.length > 0
) {
truncated = truncated.slice(0, -1);
}
if (truncated.length < text.length) {
truncated = truncated.slice(0, -1) + '';
}
// 最终宽高
const finalWidth = font.widthOfTextAtSize(truncated, size);
const finalHeight = font.heightAtSize(size);
// 计算居中坐标(pdf-lib 原点在左下)
const drawX = x + (width - finalWidth) / 2;
const drawY = y + (height - finalHeight) / 2;
page.drawText(truncated, {
x: drawX,
y: drawY,
size,
font,
color: rgb(0, 0, 0),
maxWidth: width,
lineHeight: size * 1.1,
});
}
}
import { ApiProperty } from '@nestjs/swagger';
export class FileUploadDto {
@ApiProperty({
type: 'string',
format: 'binary',
})
file: string;
}
import { IsDate, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class FillOptionsDto {
@IsString()
@IsOptional()
name?: string;
@IsDate()
@IsOptional()
date?: Date;
@IsString()
@IsOptional()
event?: string;
@IsString()
@IsOptional()
lastInitial?: string;
}
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.setGlobalPrefix('api/v2');
app.enableCors();
app.set('trust proxy', ['172.16.0.0/12', 'loopback']);
const config = app.get(ConfigService);
if (!config.get('NO_OPENAPI')) {
const documentConfig = new DocumentBuilder()
.setTitle('app')
.setDescription('The app')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, documentConfig);
SwaggerModule.setup('docs', app, document);
}
await app.listen(
config.get<number>('port') || 3000,
config.get<string>('host') || '::',
);
}
bootstrap();
import yaml from 'yaml';
import * as fs from 'fs';
const defaultConfig = {
host: '::',
port: 3000,
};
export type Config = typeof defaultConfig;
export async function loadConfig(): Promise<Config> {
let readConfig: Partial<Config> = {};
try {
const configText = await fs.promises.readFile('./config.yaml', 'utf-8');
readConfig = yaml.parse(configText);
} catch (e) {
console.error(`Failed to read config: ${e.toString()}`);
}
return {
...defaultConfig,
...readConfig,
...process.env,
};
}
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
/* it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
}); */
});
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"esModuleInterop": true
},
"compileOnSave": true,
"allowJs": true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment