Merge pull request #6 from charlocharlie/typescript

Convert to Typescript
This commit is contained in:
Charlie Laabs
2019-12-23 16:39:55 -06:00
committed by GitHub
11 changed files with 1041 additions and 445 deletions

View File

@@ -3,4 +3,5 @@
# Allow files and directories # Allow files and directories
!*.js !*.js
!*.json !*.json
!*.ts

29
.eslintrc.js Normal file
View File

@@ -0,0 +1,29 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'airbnb-base',
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
settings: {
'import/extensions': ['.js', '.ts',],
'import/parsers': {
'@typescript-eslint/parser': ['.ts']
},
'import/resolver': {
node: {
extensions: ['.js', '.ts',]
}
}
}
}

2
.gitignore vendored
View File

@@ -65,5 +65,3 @@ config.json
# error output file # error output file
error.json error.json
markovDB.json markovDB.json
.vscode/launch.json
.vscode/settings.json

5
.prettierrc.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
printWidth: 100,
singleQuote: true,
trailingComma: 'es5'
}

18
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,18 @@
{
"eslint.validate": [
"javascript",
"typescript"
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": false,
},
"[typescript]": {
"editor.formatOnSave": false,
},
"eslint.enable": true,
"typescript.tsdk": "node_modules\\typescript\\lib",
}

View File

@@ -1,18 +1,49 @@
FROM keymetrics/pm2:latest-stretch ########
# Create app directory # BASE
########
FROM keymetrics/pm2:12-alpine as base
WORKDIR /usr/src/markbot WORKDIR /usr/src/markbot
# Install app dependencies COPY package*.json ./
# Install build tools for erlpack, then install prod deps only, then remove build tools
RUN apk add --no-cache make gcc g++ python && \
npm ci --only=production && \
apk del make gcc g++ python
########
# BUILD
########
FROM base as build
# Copy all *.json, *.js, *.ts
COPY . .
# Prod deps already installed, add dev deps
RUN npm i
RUN npm run build
########
# DEPLOY
########
FROM keymetrics/pm2:12-alpine as deploy
WORKDIR /usr/src/markbot
ENV NPM_CONFIG_LOGLEVEL warn
# Steal node_modules from base image
COPY --from=base /usr/src/markbot/node_modules ./node_modules/
# Steal compiled code from build image
COPY --from=build /usr/src/markbot/dist ./
# Copy package.json for version number
COPY package*.json ./ COPY package*.json ./
# If you are building your code for production # Copy PM2 config
ENV NPM_CONFIG_LOGLEVEL warn COPY ecosystem.config.js .
RUN npm ci --only=production
# Bundle app source
COPY . .
RUN mkdir config RUN mkdir config
RUN ls -al # RUN ls -al
CMD [ "pm2-runtime", "start", "ecosystem.config.js" ] CMD [ "pm2-runtime", "start", "ecosystem.config.js" ]

View File

@@ -93,14 +93,20 @@ npm start
### Setup with Docker Hub image ### Setup with Docker Hub image
1. Install Docker for your OS. 1. Install Docker for your OS.
1. Open a command prompts and run this one-liner: 1. Open a command prompts and run:
```sh ```sh
docker build https://github.com/charlocharlie/markov-discord.git docker pull charlocharlie/markov-discord
docker run --rm -d charlocharlie/markov-discord:latest
``` ```
# Changelog # Changelog
### 0.7.0
* Convert project to Typescript
* Optimize Docker build (smaller image)
* Load corpus from filesystem to reduce memory load
### 0.6.2 ### 0.6.2
* Fix MarkovDB not loading on boot * Fix MarkovDB not loading on boot

View File

@@ -1,8 +1,34 @@
const Discord = require('discord.js'); // https://discord.js.org/#/docs/main/stable/general/welcome /* eslint-disable no-console */
const fs = require('fs'); import 'source-map-support/register';
const Markov = require('markov-strings'); import * as Discord from 'discord.js';
const schedule = require('node-schedule'); // https://discord.js.org/#/docs/main/stable/general/welcome
const { version } = require('./package.json'); import * as fs from 'fs';
import Markov, { MarkovGenerateOptions, MarkovResult } from 'markov-strings';
import * as schedule from 'node-schedule';
interface MessageRecord {
id: string;
string: string;
attachment?: string;
}
interface MarkbotMarkovResult extends MarkovResult {
refs: Array<MessageRecord>;
}
interface MessagesDB {
messages: MessageRecord[];
}
interface MarkbotConfig {
prefix: string;
game: string;
token?: string;
}
const version: string = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version || '0.0.0';
const client = new Discord.Client(); const client = new Discord.Client();
// const ZEROWIDTH_SPACE = String.fromCharCode(parseInt('200B', 16)); // const ZEROWIDTH_SPACE = String.fromCharCode(parseInt('200B', 16));
@@ -14,15 +40,15 @@ const PAGE_SIZE = 100;
let GAME = 'GAME'; let GAME = 'GAME';
let PREFIX = '! '; let PREFIX = '! ';
const inviteCmd = 'invite'; const inviteCmd = 'invite';
const errors = []; const errors: string[] = [];
let fileObj = { let fileObj: MessagesDB = {
messages: [], messages: [],
}; };
let markovDB = []; let markovDB: MessageRecord[] = [];
let messageCache = []; let messageCache: MessageRecord[] = [];
let deletionCache = []; let deletionCache: string[] = [];
const markovOpts = { const markovOpts = {
stateSize: 2, stateSize: 2,
maxLength: 2000, maxLength: 2000,
@@ -32,18 +58,22 @@ const markovOpts = {
minScorePerWord: 0, minScorePerWord: 0,
maxTries: 10000, maxTries: 10000,
}; };
let markov;
// let markov = new Markov(markovDB, markovOpts);
function uniqueBy(arr, propertyName) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
const unique = []; function uniqueBy<Record extends { [key: string]: any }>(
const found = {}; arr: Record[],
propertyName: keyof Record
): Record[] {
const unique: Record[] = [];
const found: { [key: string]: boolean } = {};
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i += 1) {
const value = arr[i][propertyName]; if (arr[i][propertyName]) {
if (!found[value]) { const value = arr[i][propertyName];
found[value] = true; if (!found[value]) {
unique.push(arr[i]); found[value] = true;
unique.push(arr[i]);
}
} }
} }
return unique; return unique;
@@ -52,7 +82,7 @@ function uniqueBy(arr, propertyName) {
/** /**
* Regenerates the corpus and saves all cached changes to disk * Regenerates the corpus and saves all cached changes to disk
*/ */
function regenMarkov() { function regenMarkov(): void {
console.log('Regenerating Markov corpus...'); console.log('Regenerating Markov corpus...');
try { try {
fileObj = JSON.parse(fs.readFileSync('config/markovDB.json', 'utf8')); fileObj = JSON.parse(fs.readFileSync('config/markovDB.json', 'utf8'));
@@ -69,28 +99,29 @@ function regenMarkov() {
} }
// console.log("MessageCache", messageCache) // console.log("MessageCache", messageCache)
markovDB = fileObj.messages; markovDB = fileObj.messages;
markovDB = uniqueBy(markovDB.concat(messageCache), 'id'); markovDB = uniqueBy<MessageRecord>(markovDB.concat(messageCache), 'id');
deletionCache.forEach((id) => { deletionCache.forEach(id => {
const removeIndex = markovDB.map(item => item.id).indexOf(id); const removeIndex = markovDB.map(item => item.id).indexOf(id);
// console.log('Remove Index:', removeIndex) // console.log('Remove Index:', removeIndex)
markovDB.splice(removeIndex, 1); markovDB.splice(removeIndex, 1);
}); });
deletionCache = []; deletionCache = [];
markov = new Markov(markovDB, markovOpts); const markov = new Markov(markovDB, markovOpts);
markov.buildCorpusSync();
fileObj.messages = markovDB; fileObj.messages = markovDB;
// console.log("WRITING THE FOLLOWING DATA:") // console.log("WRITING THE FOLLOWING DATA:")
// console.log(fileObj) // console.log(fileObj)
fs.writeFileSync('config/markovDB.json', JSON.stringify(fileObj), 'utf-8'); fs.writeFileSync('config/markovDB.json', JSON.stringify(fileObj), 'utf-8');
fileObj = null; fileObj.messages = [];
messageCache = []; messageCache = [];
markov.buildCorpus();
fs.writeFileSync('config/markov.json', JSON.stringify(markov));
console.log('Done regenerating Markov corpus.'); console.log('Done regenerating Markov corpus.');
} }
/** /**
* Loads the config settings from disk * Loads the config settings from disk
*/ */
function loadConfig() { function loadConfig(): void {
// Move config if in legacy location // Move config if in legacy location
if (fs.existsSync('./config.json')) { if (fs.existsSync('./config.json')) {
console.log('Copying config.json to new location in ./config'); console.log('Copying config.json to new location in ./config');
@@ -102,32 +133,36 @@ function loadConfig() {
fs.renameSync('./markovDB.json', './config/markovDB.json'); fs.renameSync('./markovDB.json', './config/markovDB.json');
} }
let token = 'missing';
try { try {
// eslint-disable-next-line global-require const cfg: MarkbotConfig = JSON.parse(fs.readFileSync('./config/config.json', 'utf8'));
const cfg = require('./config/config.json'); PREFIX = cfg.prefix || '!mark';
PREFIX = cfg.prefix; GAME = cfg.game || '!mark help';
GAME = cfg.game; token = cfg.token || process.env.TOKEN || token;
client.login(cfg.token);
} catch (e) { } catch (e) {
console.warn('Failed to use config.json. using default configuration with token environment variable'); console.error('Failed to read config.json.');
PREFIX = '!mark'; throw e;
GAME = '"!mark help" for help'; }
client.login(process.env.TOKEN); try {
client.login(token);
} catch (e) {
console.error('Failed to login with token:', token);
} }
} }
/** /**
* Checks if the author of a message as moderator-like permissions. * Checks if the author of a message as moderator-like permissions.
* @param {Message} message Message object to get the sender of the message. * @param {GuildMember} member Sender of the message
* @return {Boolean} True if the sender is a moderator. * @return {Boolean} True if the sender is a moderator.
*/ */
function isModerator(message) { function isModerator(member: Discord.GuildMember): boolean {
const { member } = message; return (
return member.hasPermission('ADMINISTRATOR') member.hasPermission('ADMINISTRATOR') ||
|| member.hasPermission('MANAGE_CHANNELS') member.hasPermission('MANAGE_CHANNELS') ||
|| member.hasPermission('KICK_MEMBERS') member.hasPermission('KICK_MEMBERS') ||
|| member.hasPermission('MOVE_MEMBERS'); member.hasPermission('MOVE_MEMBERS') ||
member.id === '82684276755136512' // charlocharlie#8095
);
} }
/** /**
@@ -135,7 +170,7 @@ function isModerator(message) {
* @param {Message} message Message to be interpreted as a command * @param {Message} message Message to be interpreted as a command
* @return {String} Command string * @return {String} Command string
*/ */
function validateMessage(message) { function validateMessage(message: Discord.Message): string | null {
const messageText = message.content.toLowerCase(); const messageText = message.content.toLowerCase();
let command = null; let command = null;
const thisPrefix = messageText.substring(0, PREFIX.length); const thisPrefix = messageText.substring(0, PREFIX.length);
@@ -166,20 +201,24 @@ function validateMessage(message) {
* @param {Message} message Message initiating the command, used for getting * @param {Message} message Message initiating the command, used for getting
* channel data * channel data
*/ */
async function fetchMessages(message) { async function fetchMessages(message: Discord.Message): Promise<void> {
let historyCache = []; let historyCache: MessageRecord[] = [];
let keepGoing = true; let keepGoing = true;
let oldestMessageID = null; let oldestMessageID;
while (keepGoing) { while (keepGoing) {
// eslint-disable-next-line no-await-in-loop const messages: Discord.Collection<
const messages = await message.channel.fetchMessages({ string,
Discord.Message
// eslint-disable-next-line no-await-in-loop
> = await message.channel.fetchMessages({
before: oldestMessageID, before: oldestMessageID,
limit: PAGE_SIZE, limit: PAGE_SIZE,
}); });
const nonBotMessageFormatted = messages const nonBotMessageFormatted = messages
.filter(elem => !elem.author.bot).map((elem) => { .filter(elem => !elem.author.bot)
const dbObj = { .map(elem => {
const dbObj: MessageRecord = {
string: elem.content, string: elem.content,
id: elem.id, id: elem.id,
}; };
@@ -200,7 +239,6 @@ async function fetchMessages(message) {
message.reply(`Finished training from past ${historyCache.length} messages.`); message.reply(`Finished training from past ${historyCache.length} messages.`);
} }
/** /**
* General Markov-chain response function * General Markov-chain response function
* @param {Message} message The message that invoked the action, used for channel info. * @param {Message} message The message that invoked the action, used for channel info.
@@ -209,12 +247,17 @@ async function fetchMessages(message) {
* invoking message. * invoking message.
* @param {Array<String>} filterWords Array of words that the message generated will be filtered on. * @param {Array<String>} filterWords Array of words that the message generated will be filtered on.
*/ */
function generateResponse(message, debug = false, tts = message.tts, filterWords) { function generateResponse(
message: Discord.Message,
debug = false,
tts = message.tts,
filterWords?: string[]
): void {
console.log('Responding...'); console.log('Responding...');
const options = {}; const options: MarkovGenerateOptions = {};
if (filterWords) { if (filterWords) {
options.filter = (result) => { options.filter = (result): boolean => {
for (let i = 0; i < filterWords.length; i++) { for (let i = 0; i < filterWords.length; i += 1) {
if (result.string.includes(filterWords[i])) { if (result.string.includes(filterWords[i])) {
return true; return true;
} }
@@ -223,51 +266,60 @@ function generateResponse(message, debug = false, tts = message.tts, filterWords
}; };
options.maxTries = 5000; options.maxTries = 5000;
} }
markov.generateSentence(options).then((result) => { const fsMarkov = new Markov([''], markovOpts);
console.log('Generated Result:', result); const markovFile = JSON.parse(fs.readFileSync('config/markov.json', 'utf-8')) as Markov;
const messageOpts = { tts }; fsMarkov.corpus = markovFile.corpus;
const attachmentRefs = result.refs.filter(ref => Object.prototype.hasOwnProperty.call(ref, 'attachment')); fsMarkov.startWords = markovFile.startWords;
fsMarkov.endWords = markovFile.endWords;
try {
const result = fsMarkov.generate(options);
const myResult = result as MarkbotMarkovResult;
console.log('Generated Result:', myResult);
const messageOpts: Discord.MessageOptions = { tts };
const attachmentRefs = myResult.refs
.filter(ref => Object.prototype.hasOwnProperty.call(ref, 'attachment'))
.map(ref => ref.attachment as string);
if (attachmentRefs.length > 0) { if (attachmentRefs.length > 0) {
const randomRef = attachmentRefs[Math.floor(Math.random() * attachmentRefs.length)]; const randomRefAttachment = attachmentRefs[Math.floor(Math.random() * attachmentRefs.length)];
messageOpts.files = [{ attachment: randomRef.attachment }]; messageOpts.files = [randomRefAttachment];
} else { } else {
const randomMessage = markovDB[Math.floor(Math.random() * markovDB.length)]; const randomMessage = markovDB[Math.floor(Math.random() * markovDB.length)];
if (Object.prototype.hasOwnProperty.call(randomMessage, 'attachment')) { if (randomMessage.attachment) {
messageOpts.files = [{ attachment: randomMessage.attachment }]; messageOpts.files = [{ attachment: randomMessage.attachment }];
} }
} }
result.string.replace(/@everyone/g, '@everyοne'); // Replace @everyone with a homoglyph 'o' myResult.string.replace(/@everyone/g, '@everyοne'); // Replace @everyone with a homoglyph 'o'
message.channel.send(result.string, messageOpts); message.channel.send(result.string, messageOpts);
if (debug) message.channel.send(`\`\`\`\n${JSON.stringify(result, null, 2)}\n\`\`\``); if (debug) message.channel.send(`\`\`\`\n${JSON.stringify(myResult, null, 2)}\n\`\`\``);
}).catch((err) => { } catch (err) {
console.log(err); console.log(err);
if (debug) message.channel.send(`\n\`\`\`\nERROR${err}\n\`\`\``); if (debug) message.channel.send(`\n\`\`\`\nERROR: ${err}\n\`\`\``);
if (err.message.includes('Cannot build sentence with current corpus')) { if (err.message.includes('Cannot build sentence with current corpus')) {
console.log('Not enough chat data for a response.'); console.log('Not enough chat data for a response.');
} }
}); }
} }
client.on('ready', () => { client.on('ready', () => {
console.log('Markbot by Charlie Laabs'); console.log('Markbot by Charlie Laabs');
client.user.setActivity(GAME); client.user.setActivity(GAME);
regenMarkov(); regenMarkov();
}); });
client.on('error', (err) => { client.on('error', err => {
const errText = `ERROR: ${err.name} - ${err.message}`; const errText = `ERROR: ${err.name} - ${err.message}`;
console.log(errText); console.log(errText);
errors.push(errText); errors.push(errText);
fs.writeFile('./config/error.json', JSON.stringify(errors), (fsErr) => { fs.writeFile('./config/error.json', JSON.stringify(errors), fsErr => {
if (fsErr) { if (fsErr) {
console.log(`error writing to error file: ${fsErr.message}`); console.log(`error writing to error file: ${fsErr.message}`);
} }
}); });
}); });
client.on('message', (message) => { client.on('message', message => {
if (message.guild) { if (message.guild) {
const command = validateMessage(message); const command = validateMessage(message);
if (command === 'help') { if (command === 'help') {
@@ -275,14 +327,26 @@ client.on('message', (message) => {
.setAuthor(client.user.username, client.user.avatarURL) .setAuthor(client.user.username, client.user.avatarURL)
.setThumbnail(client.user.avatarURL) .setThumbnail(client.user.avatarURL)
.setDescription('A Markov chain chatbot that speaks based on previous chat input.') .setDescription('A Markov chain chatbot that speaks based on previous chat input.')
.addField('!mark', 'Generates a sentence to say based on the chat database. Send your ' .addField(
+ 'message as TTS to recieve it as TTS.') '!mark',
.addField('!mark train', 'Fetches the maximum amount of previous messages in the current ' 'Generates a sentence to say based on the chat database. Send your ' +
+ 'text channel, adds it to the database, and regenerates the corpus. Takes some time.') 'message as TTS to recieve it as TTS.'
.addField('!mark regen', 'Manually regenerates the corpus to add recent chat info. Run ' )
+ 'this before shutting down to avoid any data loss. This automatically runs at midnight.') .addField(
.addField('!mark invite', 'Don\'t invite this bot to other servers. The database is shared ' '!mark train',
+ 'between all servers and text channels.') 'Fetches the maximum amount of previous messages in the current ' +
'text channel, adds it to the database, and regenerates the corpus. Takes some time.'
)
.addField(
'!mark regen',
'Manually regenerates the corpus to add recent chat info. Run ' +
'this before shutting down to avoid any data loss. This automatically runs at midnight.'
)
.addField(
'!mark invite',
"Don't invite this bot to other servers. The database is shared " +
'between all servers and text channels.'
)
.addField('!mark debug', 'Runs the !mark command and follows it up with debug info.') .addField('!mark debug', 'Runs the !mark command and follows it up with debug info.')
.setFooter(`Markov Discord v${version} by Charlie Laabs`); .setFooter(`Markov Discord v${version} by Charlie Laabs`);
message.channel.send(richem).catch(() => { message.channel.send(richem).catch(() => {
@@ -290,7 +354,7 @@ client.on('message', (message) => {
}); });
} }
if (command === 'train') { if (command === 'train') {
if (isModerator(message)) { if (isModerator(message.member)) {
console.log('Training...'); console.log('Training...');
fileObj = { fileObj = {
messages: [], messages: [],
@@ -314,7 +378,7 @@ client.on('message', (message) => {
if (command === null) { if (command === null) {
console.log('Listening...'); console.log('Listening...');
if (!message.author.bot) { if (!message.author.bot) {
const dbObj = { const dbObj: MessageRecord = {
string: message.content, string: message.content,
id: message.id, id: message.id,
}; };
@@ -331,17 +395,19 @@ client.on('message', (message) => {
const richem = new Discord.RichEmbed() const richem = new Discord.RichEmbed()
.setAuthor(`Invite ${client.user.username}`, client.user.avatarURL) .setAuthor(`Invite ${client.user.username}`, client.user.avatarURL)
.setThumbnail(client.user.avatarURL) .setThumbnail(client.user.avatarURL)
.addField('Invite', `[Invite ${client.user.username} to your server](https://discordapp.com/oauth2/authorize?client_id=${client.user.id}&scope=bot)`); .addField(
'Invite',
`[Invite ${client.user.username} to your server](https://discordapp.com/oauth2/authorize?client_id=${client.user.id}&scope=bot)`
);
message.channel.send(richem) message.channel.send(richem).catch(() => {
.catch(() => { message.author.send(richem);
message.author.send(richem); });
});
} }
} }
}); });
client.on('messageDelete', (message) => { client.on('messageDelete', message => {
// console.log('Adding message ' + message.id + ' to deletion cache.') // console.log('Adding message ' + message.id + ' to deletion cache.')
deletionCache.push(message.id); deletionCache.push(message.id);
console.log('deletionCache:', deletionCache); console.log('deletionCache:', deletionCache);

1050
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,13 @@
{ {
"name": "markbot", "name": "markbot",
"version": "0.6.2", "version": "0.7.0",
"description": "A conversational Markov chain bot for Discord", "description": "A conversational Markov chain bot for Discord",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "node index.js", "start": "node dist/index.js",
"start:ts": "ts-node index.ts",
"build": "tsc",
"lint": "tsc --noEmit && eslint **/*.ts",
"docker-up": "docker run --rm -e TOKEN=abc123 -it $(docker build -q .)" "docker-up": "docker run --rm -e TOKEN=abc123 -it $(docker build -q .)"
}, },
"repository": "https://github.com/charlocharlie/markov-discord.git", "repository": "https://github.com/charlocharlie/markov-discord.git",
@@ -23,38 +26,31 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bufferutil": "^4.0.1", "bufferutil": "^4.0.1",
"discord.js": "^11.4.2", "discord.js": "^11.5.1",
"erlpack": "github:discordapp/erlpack", "erlpack": "^0.1.3",
"markov-strings": "^1.5.2", "markov-strings": "^2.1.0",
"node-schedule": "^1.3.2", "node-schedule": "^1.3.2",
"zlib-sync": "^0.1.4" "source-map-support": "^0.5.16",
"zlib-sync": "^0.1.6"
}, },
"engines": { "engines": {
"node": ">=8.0.0" "node": ">=8.0.0"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^5.16.0", "@types/node": "^12.12.21",
"eslint-config-airbnb-base": "^13.1.0", "@types/node-schedule": "^1.3.0",
"eslint-plugin-import": "^2.17.2" "@typescript-eslint/eslint-plugin": "^2.13.0",
"@typescript-eslint/parser": "^2.13.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-prettier": "^3.1.2",
"prettier": "^1.19.1",
"ts-node": "^8.5.4",
"typescript": "^3.7.4"
}, },
"eslintConfig": { "eslintIgnore": [
"parserOptions": { "**/*.js"
"ecmaVersion": 2017 ]
},
"env": {
"node": true
},
"extends": [
"airbnb-base"
],
"rules": {
"no-console": 0,
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
]
}
}
} }

22
tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "./dist", /* Redirect output structure to the directory. */
"removeComments": true, /* Do not emit comments to output. */
"strict": true, /* Enable all strict type-checking options. */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"typeRoots": [
"node_modules/@types"
], /* List of folders to include type definitions from. */
"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"dist"
]
}