Commit 738bf7e2 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
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
# 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
- combine
- deploy
variables:
GIT_DEPTH: "1"
CONTAINER_TEST_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
CONTAINER_TEST_ARM_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-arm
CONTAINER_TEST_X86_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-x86
CONTAINER_RELEASE_IMAGE: $CI_REGISTRY_IMAGE:latest
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
build-x86:
stage: build
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_TEST_X86_IMAGE
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
build-arm:
stage: build
tags:
- docker-arm
script:
- TARGET_IMAGE=$CONTAINER_TEST_ARM_IMAGE
- docker build --pull -t $TARGET_IMAGE .
- docker push $TARGET_IMAGE
combine:
stage: combine
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_TEST_IMAGE
- SOURCE_IMAGE_2=$CONTAINER_TEST_ARM_IMAGE
- SOURCE_IMAGE_1=$CONTAINER_TEST_X86_IMAGE
- docker pull $SOURCE_IMAGE_1
- docker pull $SOURCE_IMAGE_2
- docker manifest create $TARGET_IMAGE --amend $SOURCE_IMAGE_1 --amend
$SOURCE_IMAGE_2
- docker manifest push $TARGET_IMAGE
deploy_latest:
stage: deploy
tags:
- docker
script:
- TARGET_IMAGE=$CONTAINER_RELEASE_IMAGE
- SOURCE_IMAGE=$CONTAINER_TEST_IMAGE
- docker pull $SOURCE_IMAGE
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
- docker push $TARGET_IMAGE
only:
- master
deploy_tag:
stage: deploy
tags:
- docker
script:
- TARGET_IMAGE=$CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
- SOURCE_IMAGE=$CONTAINER_TEST_IMAGE
- docker pull $SOURCE_IMAGE
- docker tag $SOURCE_IMAGE $TARGET_IMAGE
- docker push $TARGET_IMAGE
only:
- tags
/install-npm.sh
.git*
/data
/output
/config.yaml
.idea
.dockerignore
Dockerfile
/src
{
"singleQuote": true,
"trailingComma": "all"
}
\ No newline at end of file
FROM node:bullseye-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/*
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
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
#!/bin/bash
npm install --save \
class-validator \
class-transformer \
@nestjs/swagger \
@nestjs/config \
swagger-ui-express \
yaml
npm install --save-dev \
@types/express
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": ["@nestjs/swagger"]
}
}
This diff is collapsed.
{
"name": "momento",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"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": {
"@koishijs/plugin-assets-s3": "^1.0.0-beta.4",
"@nestjs/common": "^8.0.0",
"@nestjs/config": "^1.1.5",
"@nestjs/core": "^8.0.0",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/platform-ws": "^8.2.4",
"@nestjs/swagger": "^5.1.5",
"@nestjs/websockets": "^8.2.4",
"class-transformer": "^0.4.0",
"class-validator": "^0.13.2",
"koishi": "^4.0.0-beta.7",
"koishi-nestjs": "^2.4.1",
"qface": "^1.2.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"swagger-ui-express": "^4.3.0",
"ws": "^8.4.0",
"yaml": "^1.10.2"
},
"devDependencies": {
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.1",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^4.28.2",
"@typescript-eslint/parser": "^4.28.2",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.6",
"prettier": "^2.3.2",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
exports.__esModule = true;
exports.AppModule = void 0;
var common_1 = require("@nestjs/common");
var config_1 = require("@nestjs/config");
var config_2 = require("./utility/config");
var koishi_nestjs_1 = require("koishi-nestjs");
var koishi_plugin_adapter_onebot_ex_1 = require("koishi-plugin-adapter-onebot-ex");
var message_service_1 = require("./message/message.service");
var plugin_assets_s3_1 = require("@koishijs/plugin-assets-s3");
var AppModule = /** @class */ (function () {
function AppModule() {
}
AppModule = __decorate([
(0, common_1.Module)({
imports: [
config_1.ConfigModule.forRoot({
ignoreEnvVars: true,
load: [config_2.loadConfig],
isGlobal: true
}),
koishi_nestjs_1.KoishiModule.registerAsync({
inject: [config_1.ConfigService],
useFactory: function (configService) { return __awaiter(void 0, void 0, void 0, function () {
var onebotConfig, s3Config;
return __generator(this, function (_a) {
onebotConfig = configService.get('onebot');
s3Config = configService.get('s3');
return [2 /*return*/, {
prefix: '__never_prefix',
minSimilarity: 1,
help: false,
useWs: true,
usePlugins: __spreadArray([
(0, koishi_nestjs_1.PluginDef)(koishi_plugin_adapter_onebot_ex_1["default"], onebotConfig)
], (s3Config ? [(0, koishi_nestjs_1.PluginDef)(plugin_assets_s3_1["default"], s3Config)] : []), true)
}];
});
}); }
}),
],
providers: [message_service_1.MessageService]
})
], AppModule);
return AppModule;
}());
exports.AppModule = AppModule;
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { loadConfig } from './utility/config';
import { KoishiModule, PluginDef } from 'koishi-nestjs';
import AdapterOnebot, { BotConfig } from './onebot-improved';
import { Adapter } from 'koishi';
import { AdapterConfig } from './onebot-improved/utils';
import { MessageService } from './message/message.service';
import S3Assets from '@koishijs/plugin-assets-s3';
@Module({
imports: [
ConfigModule.forRoot({
ignoreEnvVars: true,
load: [loadConfig],
isGlobal: true,
}),
KoishiModule.registerAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const onebotConfig =
configService.get<Adapter.PluginConfig<AdapterConfig, BotConfig>>(
'onebot',
);
const s3Config = configService.get<S3Assets.Config>('s3');
return {
prefix: '__never_prefix',
minSimilarity: 1,
help: false,
useWs: true,
usePlugins: [
PluginDef(AdapterOnebot, onebotConfig),
...(s3Config ? [PluginDef(S3Assets, s3Config)] : []),
],
};
},
}),
],
providers: [MessageService],
})
export class AppModule {}
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
exports.__esModule = true;
exports.ReturnMessageDto = exports.BlankReturnMessageDto = void 0;
var openapi = require("@nestjs/swagger");
var swagger_1 = require("@nestjs/swagger");
var common_1 = require("@nestjs/common");
var BlankReturnMessageDto = /** @class */ (function () {
function BlankReturnMessageDto(statusCode, message) {
this.statusCode = statusCode;
this.message = message || 'success';
this.success = statusCode < 400;
}
BlankReturnMessageDto.prototype.toException = function () {
return new common_1.HttpException(this, this.statusCode);
};
BlankReturnMessageDto._OPENAPI_METADATA_FACTORY = function () {
return { statusCode: { required: true, type: function () { return Number; } }, message: { required: true, type: function () { return String; } }, success: { required: true, type: function () { return Boolean; } } };
};
__decorate([
(0, swagger_1.ApiProperty)({ description: '返回状态' })
], BlankReturnMessageDto.prototype, "statusCode");
__decorate([
(0, swagger_1.ApiProperty)({ description: '返回信息' })
], BlankReturnMessageDto.prototype, "message");
__decorate([
(0, swagger_1.ApiProperty)({ description: '是否成功' })
], BlankReturnMessageDto.prototype, "success");
return BlankReturnMessageDto;
}());
exports.BlankReturnMessageDto = BlankReturnMessageDto;
var ReturnMessageDto = /** @class */ (function (_super) {
__extends(ReturnMessageDto, _super);
function ReturnMessageDto(statusCode, message, data) {
var _this = _super.call(this, statusCode, message) || this;
_this.data = data;
return _this;
}
ReturnMessageDto._OPENAPI_METADATA_FACTORY = function () {
return { data: { required: false } };
};
__decorate([
(0, swagger_1.ApiProperty)({ description: '返回内容' })
], ReturnMessageDto.prototype, "data");
return ReturnMessageDto;
}(BlankReturnMessageDto));
exports.ReturnMessageDto = ReturnMessageDto;
import { ApiProperty } from '@nestjs/swagger';
import { HttpException } from '@nestjs/common';
export interface BlankReturnMessage {
statusCode: number;
message: string;
success: boolean;
}
export interface ReturnMessage<T> extends BlankReturnMessage {
data?: T;
}
export class BlankReturnMessageDto implements BlankReturnMessage {
@ApiProperty({ description: '返回状态' })
statusCode: number;
@ApiProperty({ description: '返回信息' })
message: string;
@ApiProperty({ description: '是否成功' })
success: boolean;
constructor(statusCode: number, message?: string) {
this.statusCode = statusCode;
this.message = message || 'success';
this.success = statusCode < 400;
}
toException() {
return new HttpException(this, this.statusCode);
}
}
export class ReturnMessageDto<T>
extends BlankReturnMessageDto
implements ReturnMessage<T>
{
@ApiProperty({ description: '返回内容' })
data?: T;
constructor(statusCode: number, message?: string, data?: T) {
super(statusCode, message);
this.data = data;
}
}
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var core_1 = require("@nestjs/core");
var swagger_1 = require("@nestjs/swagger");
var app_module_1 = require("./app.module");
var config_1 = require("@nestjs/config");
function bootstrap() {
return __awaiter(this, void 0, void 0, function () {
var app, documentConfig, document, config;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, core_1.NestFactory.create(app_module_1.AppModule)];
case 1:
app = _a.sent();
app.enableCors();
app.set('trust proxy', ['172.16.0.0/12', 'loopback']);
documentConfig = new swagger_1.DocumentBuilder()
.setTitle('momento')
.setDescription('The app')
.setVersion('1.0')
.build();
document = swagger_1.SwaggerModule.createDocument(app, documentConfig);
swagger_1.SwaggerModule.setup('docs', app, document);
config = app.get(config_1.ConfigService);
return [4 /*yield*/, app.listen(config.get('port') || 3000, config.get('host') || '::')];
case 2:
_a.sent();
return [2 /*return*/];
}
});
});
}
bootstrap();
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.enableCors();
app.set('trust proxy', ['172.16.0.0/12', 'loopback']);
const documentConfig = new DocumentBuilder()
.setTitle('momento')
.setDescription('The app')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, documentConfig);
SwaggerModule.setup('docs', app, document);
const config = app.get(ConfigService);
await app.listen(
config.get<number>('port') || 3000,
config.get<string>('host') || '::',
);
}
bootstrap();
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
exports.MessageService = void 0;
var common_1 = require("@nestjs/common");
var koishi_nestjs_1 = require("koishi-nestjs");
var MessageService = /** @class */ (function (_super) {
__extends(MessageService, _super);
function MessageService(ctx) {
var _this = _super.call(this, 'message') || this;
_this.ctx = ctx;
return _this;
}
MessageService.prototype.onApplicationBootstrap = function () {
if (this.assets) {
this.log("Assets transformation enabled.");
}
};
MessageService.prototype.onMessage = function (session) {
return __awaiter(this, void 0, void 0, function () {
var content, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
content = session.content;
if (!this.assets) return [3 /*break*/, 4];
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.assets.transform(content)];
case 2:
content = _a.sent();
return [3 /*break*/, 4];
case 3:
e_1 = _a.sent();
content = session.content;
this.error("Failed to transform assets in message: ".concat(e_1.message));
return [3 /*break*/, 4];
case 4:
this.log("".concat(session.userId, " received message from ").concat(session.username, ": ").concat(content));
return [2 /*return*/];
}
});
});
};
__decorate([
(0, koishi_nestjs_1.UseEvent)('message')
], MessageService.prototype, "onMessage");
__decorate([
(0, koishi_nestjs_1.WireContextService)()
], MessageService.prototype, "assets");
MessageService = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, koishi_nestjs_1.InjectContext)())
], MessageService);
return MessageService;
}(common_1.ConsoleLogger));
exports.MessageService = MessageService;
import { Test, TestingModule } from '@nestjs/testing';
import { MessageService } from './message.service';
describe('MessageService', () => {
let service: MessageService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MessageService],
}).compile();
service = module.get<MessageService>(MessageService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import {
ConsoleLogger,
Injectable,
OnApplicationBootstrap,
} from '@nestjs/common';
import { InjectContext, UseEvent, WireContextService } from 'koishi-nestjs';
import { Context, Session } from 'koishi';
import S3Assets from '@koishijs/plugin-assets-s3';
@Injectable()
export class MessageService
extends ConsoleLogger
implements OnApplicationBootstrap {
constructor(@InjectContext() private ctx: Context) {
super('message');
}
onApplicationBootstrap(): any {
if (this.assets) {
this.log(`Assets transformation enabled.`);
}
}
@UseEvent('message')
async onMessage(session: Session) {
let content = session.content;
if (this.assets) {
try {
content = await this.assets.transform(content);
} catch (e) {
content = session.content;
this.error(`Failed to transform assets in message: ${e.message}`);
}
}
// targetId, for private messages only
const fromId = session.userId;
const targetId = session.targetId || session.selfId;
this.log(
`Received message from ${session.username}(${fromId}) to ${targetId}: ${content}`,
);
}
@WireContextService()
private assets: S3Assets;
}
This diff is collapsed.
import {
Adapter,
Logger,
assertProperty,
Schema,
Quester,
omit,
Context,
} from 'koishi';
import { BotConfig, OneBotBot } from './bot';
import { dispatchSession, AdapterConfig } from './utils';
import { createHmac } from 'crypto';
const logger = new Logger('onebot');
export class HttpServer extends Adapter<BotConfig, AdapterConfig> {
static schema: Schema<BotConfig> = Schema.object({
selfId: Schema.string().description('机器人的账号。').required(),
token: Schema.string().description(
'发送信息时用于验证的字段,应与 OneBot 配置文件中的 access_token 保持一致。',
),
endpoint: Schema.string()
.description('要连接的 OneBot 服务器地址。')
.required(),
...omit(Quester.Config.dict, ['endpoint']),
});
public bots: OneBotBot[];
constructor(ctx: Context, config: AdapterConfig = {}) {
super(ctx, config);
assertProperty(ctx.app.options, 'port');
this.http = ctx.http.extend(config.request);
}
async connect(bot: OneBotBot) {
const { endpoint, token } = bot.config;
if (!endpoint) return;
const http = this.http.extend(bot.config).extend({
headers: {
'Content-Type': 'application/json',
Authorization: `Token ${token}`,
},
});
bot.internal._request = async (action, params) => {
return http.post('/' + action, params);
};
Object.assign(bot, await bot.getSelf());
logger.info('connected to %c', http.config.endpoint);
await bot.initializeGuildServiceProfile();
bot.resolve();
}
async start() {
const { secret, path = '/onebot' } = this.config;
this.ctx.router.post(path, (ctx) => {
if (secret) {
// no signature
const signature = ctx.headers['x-signature'];
if (!signature) return (ctx.status = 401);
// invalid signature
const sig = createHmac('sha1', secret)
.update(ctx.request.rawBody)
.digest('hex');
if (signature !== `sha1=${sig}`) return (ctx.status = 403);
}
const selfId = ctx.headers['x-self-id'].toString();
const bot = this.bots.find((bot) => bot.selfId === selfId);
if (!bot) return (ctx.status = 403);
logger.debug('receive %o', ctx.request.body);
dispatchSession(bot, ctx.request.body);
});
}
stop() {
logger.debug('http server closing');
}
}
import { Adapter } from 'koishi';
import { OneBotBot } from './bot';
import { WebSocketClient, WebSocketServer } from './ws';
import { HttpServer } from './http';
import * as OneBot from './types';
declare module 'koishi' {
interface Modules {
'adapter-onebot': typeof import('.');
}
interface Session {
onebot?: OneBot.Payload & OneBot.Internal;
}
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Session {
interface Events {
onebot: {
'message-reactions-updated': {};
'channel-updated': {};
'channel-created': {};
'channel-destroyed': {};
};
}
}
}
export { OneBot };
export * from './bot';
export * from './ws';
export * from './http';
export default Adapter.define(
'OneBot',
OneBotBot,
{
http: HttpServer,
ws: WebSocketClient,
'ws-reverse': WebSocketServer,
},
({ endpoint }) => {
return !endpoint ? 'ws-reverse' : endpoint.startsWith('ws') ? 'ws' : 'http';
},
);
This diff is collapsed.
import {
Adapter,
Bot,
Session,
paramCase,
segment,
Schema,
App,
defineProperty,
} from 'koishi';
import * as qface from 'qface';
import { OneBotBot } from './bot';
import * as OneBot from './types';
export * from './types';
export interface AdapterConfig
extends Adapter.WebSocketClient.Config,
App.Config.Request {
path?: string;
secret?: string;
responseTimeout?: number;
}
export const AdapterConfig: Schema<AdapterConfig> = Schema.intersect([
Schema.object({
path: Schema.string()
.description('服务器监听的路径,用于 http 和 ws-reverse 协议。')
.default('/onebot'),
secret: Schema.string().description(
'接收事件推送时用于验证的字段,应该与 OneBot 的 secret 配置保持一致。',
),
}),
Adapter.WebSocketClient.Config,
App.Config.Request,
]);
export const adaptUser = (user: OneBot.AccountInfo): Bot.User => ({
userId: user.tiny_id || user.user_id.toString(),
avatar: user.user_id
? `http://q.qlogo.cn/headimg_dl?dst_uin=${user.user_id}&spec=640`
: undefined,
username: user.nickname,
});
export const adaptGuildMember = (user: OneBot.SenderInfo): Bot.GuildMember => ({
...adaptUser(user),
nickname: user.card,
roles: [user.role],
});
export const adaptQQGuildMember = (
user: OneBot.GuildMemberInfo,
presetRole?: string,
): Bot.GuildMember => ({
userId: user.tiny_id,
username: user.nickname,
nickname: user.nickname,
roles: [...(presetRole ? [presetRole] : []), user.role.toString()],
isBot: presetRole === 'bot',
});
export const adaptAuthor = (
user: OneBot.SenderInfo,
anonymous?: OneBot.AnonymousInfo,
): Bot.Author => ({
...adaptUser(user),
nickname: anonymous?.name || user.card,
anonymous: anonymous?.flag,
roles: [user.role],
});
export function adaptMessage(
message: OneBot.Message,
bot?: OneBotBot,
): Bot.Message {
const author = adaptAuthor(message.sender, message.anonymous);
const result: Bot.Message = {
author,
userId: author.userId,
messageId: message.message_id.toString(),
timestamp: message.time * 1000,
content: segment.transform(message.message, {
at({ qq }) {
if (bot?.isGuildServiceAvailable() && qq === bot.guildProfile.userId) {
return segment.at(bot.selfId);
}
if (qq !== 'all') return segment.at(qq);
return segment('at', { type: 'all' });
},
face: ({ id }) => segment('face', { id, url: qface.getUrl(id) }),
reply: (data) => segment('quote', data),
}),
};
if (message.guild_id) {
result.guildId = message.guild_id;
result.channelId = message.channel_id;
} else if (message.group_id) {
result.guildId = result.channelId = message.group_id.toString();
} else {
result.channelId = 'private:' + author.userId;
}
return result;
}
export const adaptGuild = (
info: OneBot.GroupInfo | OneBot.GuildBaseInfo,
): Bot.Guild => {
if ((info as OneBot.GuildBaseInfo).guild_id) {
const guild = info as OneBot.GuildBaseInfo;
return {
guildId: guild.guild_id,
guildName: guild.guild_name,
};
} else {
const group = info as OneBot.GroupInfo;
return {
guildId: group.group_id.toString(),
guildName: group.group_name,
};
}
};
export const adaptChannel = (
info: OneBot.GroupInfo | OneBot.ChannelInfo,
): Bot.Channel => {
if ((info as OneBot.ChannelInfo).channel_id) {
const channel = info as OneBot.ChannelInfo;
return {
channelId: channel.channel_id.toString(),
channelName: channel.channel_name,
};
} else {
const group = info as OneBot.GroupInfo;
return {
channelId: group.group_id.toString(),
channelName: group.group_name,
};
}
};
export function dispatchSession(bot: OneBotBot, data: OneBot.Payload) {
const payload = adaptSession(data, bot);
if (!payload) return;
const session = new Session(bot, payload);
defineProperty(session, 'onebot', Object.create(bot.internal));
Object.assign(session.onebot, data);
bot.adapter.dispatch(session);
}
export function adaptSession(data: OneBot.Payload, bot?: OneBotBot) {
const session: Partial<Session> = {};
session.selfId = '' + data.self_id;
session.type = data.post_type as any;
session.subtype = data.sub_type as any;
if (data.post_type === 'message' || data.post_type === 'message_sent') {
Object.assign(session, adaptMessage(data, bot));
session.type = 'message';
session.subtype = data.message_type;
return session;
}
if (data.user_id) session.userId = '' + data.user_id;
if (data.group_id) session.guildId = session.channelId = '' + data.group_id;
if (data.guild_id) session.guildId = '' + data.guild_id;
if (data.channel_id) session.channelId = '' + data.channel_id;
if (data.target_id) session.targetId = '' + data.target_id;
if (data.operator_id) session.operatorId = '' + data.operator_id;
if (data.message_id) session.messageId = '' + data.message_id;
if (data.post_type === 'request') {
session.content = data.comment;
session.messageId = data.flag;
if (data.request_type === 'friend') {
session.type = 'friend-request';
session.channelId = `private:${session.userId}`;
} else if (data.sub_type === 'add') {
session.type = 'guild-member-request';
} else {
session.type = 'guild-request';
}
} else if (data.post_type === 'notice') {
switch (data.notice_type) {
case 'group_recall':
session.type = 'message-deleted';
session.subtype = 'group';
break;
case 'friend_recall':
session.type = 'message-deleted';
session.subtype = 'private';
session.channelId = `private:${session.userId}`;
break;
case 'friend_add':
session.type = 'friend-added';
break;
case 'group_upload':
session.type = 'group-file-added';
break;
case 'group_admin':
session.type = 'group-member';
session.subtype = 'role';
break;
case 'group_ban':
session.type = 'group-member';
session.subtype = 'ban';
break;
case 'group_decrease':
session.type =
session.userId === session.selfId
? 'group-deleted'
: 'group-member-deleted';
session.subtype =
session.userId === session.operatorId ? 'active' : 'passive';
break;
case 'group_increase':
session.type =
session.userId === session.selfId
? 'group-added'
: 'group-member-added';
session.subtype =
session.userId === session.operatorId ? 'active' : 'passive';
break;
case 'group_card':
session.type = 'group-member';
session.subtype = 'nickname';
break;
case 'notify':
session.type = 'notice';
session.subtype = paramCase(data.sub_type) as any;
if (session.subtype === 'poke') {
session.channelId ||= `private:${session.userId}`;
} else if (session.subtype === 'honor') {
session.subsubtype = paramCase(data.honor_type) as any;
}
break;
case 'message_reactions_updated':
session.type = 'onebot';
session.subtype = 'message-reactions-updated';
case 'channel_created':
session.type = 'onebot';
session.subtype = 'channel-created';
case 'channel_updated':
session.type = 'onebot';
session.subtype = 'channel-updated';
case 'channel_destroyed':
session.type = 'onebot';
session.subtype = 'channel-destroyed';
}
} else return;
return session;
}
export async function runIfFailBlank<T>(fun: () => Promise<T[]>): Promise<T[]> {
try {
return (await fun()) || [];
} catch (e) {
return [];
}
}
import {
Adapter,
Logger,
assertProperty,
Time,
Schema,
Context,
WebSocketLayer,
} from 'koishi';
import { BotConfig, OneBotBot } from './bot';
import { AdapterConfig, dispatchSession, adaptUser, Response } from './utils';
import WebSocket from 'ws';
const logger = new Logger('onebot');
export class WebSocketClient extends Adapter.WebSocketClient<
BotConfig,
AdapterConfig
> {
static schema: Schema<BotConfig> = Schema.object({
selfId: Schema.string().description('机器人的账号。').required(),
token: Schema.string().description(
'发送信息时用于验证的字段,应与 OneBot 的 access_token 配置保持一致。',
),
endpoint: Schema.string()
.description('要连接的 OneBot 服务器地址。')
.required(),
});
protected accept = accept;
prepare(bot: OneBotBot) {
const { endpoint, token } = bot.config;
const headers: Record<string, string> = {};
if (token) headers.Authorization = `Bearer ${token}`;
return new WebSocket(endpoint, { headers });
}
}
export class WebSocketServer extends Adapter<BotConfig, AdapterConfig> {
static schema: Schema<BotConfig> = Schema.object({
selfId: Schema.string().description('机器人的账号。').required(),
});
public wsServer?: WebSocketLayer;
protected accept = accept;
constructor(ctx: Context, config: AdapterConfig) {
super(ctx, config);
assertProperty(ctx.app.options, 'port');
const { path = '/onebot' } = config;
this.wsServer = ctx.router.ws(path, (socket, { headers }) => {
logger.debug('connected with', headers);
if (headers['x-client-role'] !== 'Universal') {
return socket.close(1008, 'invalid x-client-role');
}
const selfId = headers['x-self-id'].toString();
const bot = this.bots.find((bot) => bot.selfId === selfId);
if (!bot) return socket.close(1008, 'invalid x-self-id');
bot.socket = socket;
this.accept(bot as OneBotBot);
});
}
connect() {}
start() {}
stop() {
logger.debug('ws server closing');
this.wsServer.close();
for (const bot of this.bots) {
bot.socket = null;
}
}
}
let counter = 0;
const listeners: Record<number, (response: Response) => void> = {};
export function accept(
this: Adapter<BotConfig, AdapterConfig>,
bot: OneBotBot,
) {
bot.socket.on('message', (data) => {
data = data.toString();
let parsed: any;
try {
parsed = JSON.parse(data);
} catch (error) {
return logger.warn('cannot parse message', data);
}
if ('post_type' in parsed) {
logger.debug('receive %o', parsed);
dispatchSession(bot, parsed);
} else if (parsed.echo === -1) {
Object.assign(bot, adaptUser(parsed.data));
logger.debug('%d got self info', parsed.data);
bot.initializeGuildServiceProfile();
bot.resolve();
} else if (parsed.echo in listeners) {
listeners[parsed.echo](parsed);
delete listeners[parsed.echo];
}
});
bot.socket.on('close', () => {
delete bot.internal._request;
});
bot.socket.send(
JSON.stringify({
action: 'get_login_info',
echo: -1,
}),
(error) => {
if (error) bot.reject(error);
},
);
bot.internal._request = (action, params) => {
const data = { action, params, echo: ++counter };
data.echo = ++counter;
return new Promise((resolve, reject) => {
listeners[data.echo] = resolve;
setTimeout(() => {
delete listeners[data.echo];
reject(new Error('response timeout'));
}, this.config.responseTimeout || Time.minute);
bot.socket.send(JSON.stringify(data), (error) => {
if (error) reject(error);
});
});
};
}
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
exports.loadConfig = void 0;
var yaml_1 = require("yaml");
var fs = require("fs");
var defaultConfig = {
host: '::',
port: 3000,
onebot: { bots: [] }
};
function loadConfig() {
return __awaiter(this, void 0, void 0, function () {
var readConfig, configText, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
readConfig = {};
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, fs.promises.readFile('./config.yaml', 'utf-8')];
case 2:
configText = _a.sent();
readConfig = yaml_1["default"].parse(configText);
return [3 /*break*/, 4];
case 3:
e_1 = _a.sent();
console.error("Failed to read config: ".concat(e_1.toString()));
return [3 /*break*/, 4];
case 4: return [2 /*return*/, __assign(__assign({}, defaultConfig), readConfig)];
}
});
});
}
exports.loadConfig = loadConfig;
import yaml from 'yaml';
import * as fs from 'fs';
import { Adapter } from 'koishi';
import S3Assets from '@koishijs/plugin-assets-s3';
import { AdapterConfig } from '../onebot-improved/utils';
import { BotConfig } from '../onebot-improved';
export interface Config {
host: string;
port: number;
onebot: Adapter.PluginConfig<AdapterConfig, BotConfig>;
s3?: S3Assets.Config;
}
const defaultConfig: Config = {
host: '::',
port: 3000,
onebot: { bots: [] },
};
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,
};
}
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,
"skipLibCheck": 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