diff --git a/.prettierrc.cjs b/.prettierrc.cjs index ff15483..c5166c2 100644 --- a/.prettierrc.cjs +++ b/.prettierrc.cjs @@ -1,3 +1,3 @@ module.exports = { - ...require('gts/.prettierrc.json') -} + ...require('gts/.prettierrc.json'), +}; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ba88f87..abdfa88 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,9 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - // List of extensions which should be recommended for users of this workspace. - "recommendations": ["cspell.cspell", "microsoft.eslint"], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] + // List of extensions which should be recommended for users of this workspace. + "recommendations": ["cspell.cspell", "microsoft.eslint"], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 5537176..5ae5449 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,18 +1,18 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Launch Program", - "skipFiles": ["/**"], - "program": "${workspaceFolder}\\target\\core\\main.js", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": ["${workspaceFolder}/target/**/*.js"], - "args": ["--enable-source-maps"] - } - ] + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": ["/**"], + "program": "${workspaceFolder}\\target\\core\\main.js", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": ["${workspaceFolder}/target/**/*.js"], + "args": ["--enable-source-maps"] + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e24167a..e6bb422 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,16 +1,16 @@ { - "cSpell.words": [ - "channelmap", - "INITDB", - "loggingchannel", - "popconfirm", - "readcursor", - "ringbuffer", - "submod", - "turingbot", - "unlogged" - ], - "yaml.schemas": { - "kubernetes://schema/apps/v1%40deployment": "file:///c%3A/Users/James%20Boehme/Documents/GitHub/TuringBot/k8s/dev.yaml" - } + "cSpell.words": [ + "channelmap", + "INITDB", + "loggingchannel", + "popconfirm", + "readcursor", + "ringbuffer", + "submod", + "turingbot", + "unlogged" + ], + "yaml.schemas": { + "kubernetes://schema/apps/v1%40deployment": "file:///c%3A/Users/James%20Boehme/Documents/GitHub/TuringBot/k8s/dev.yaml" + } } diff --git a/Dockerfile b/Dockerfile index 9f4a5df..ca5db39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # https://nodejs.org/en/docs/guides/nodejs-docker-webapp # Defining what image we want to build from https://hub.docker.com/_/node -FROM node:16 +FROM node:20 # Create a directory for everything to be installed, this is the container working dir for future commands WORKDIR /usr/src/turing-bot diff --git a/config.default.jsonc b/config.default.jsonc index 1789be1..d8053e1 100644 --- a/config.default.jsonc +++ b/config.default.jsonc @@ -1,29 +1,29 @@ { - "logging": { - /** - * Different levels of verbose logging. - * - * 0: No logging, no events will be logged - * - * 1: Very minimalist logging, including core and module starting, as well as module failure. - * - * 2: Slightly more verbose logging, may log some important info events - * - * 3: Will log commands used and all info passed through eventLogger - * - * 4: - */ - "stdout": { - "verboseLevel": 2 - }, - // It is an option to have the bot DM certain users for events, it's suggested to leave this disabled or on a very low level. - "directMessageLogging": { - "userIds": [], - "verboseLevel": 0 - }, - "loggingChannel": { - "loggingChannelId": "", - "verboseLevel": 2 - } + "logging": { + /** + * Different levels of verbose logging. + * + * 0: No logging, no events will be logged + * + * 1: Very minimalist logging, including core and module starting, as well as module failure. + * + * 2: Slightly more verbose logging, may log some important info events + * + * 3: Will log commands used and all info passed through eventLogger + * + * 4: + */ + "stdout": { + "verboseLevel": 2 + }, + // It is an option to have the bot DM certain users for events, it's suggested to leave this disabled or on a very low level. + "directMessageLogging": { + "userIds": [], + "verboseLevel": 0 + }, + "loggingChannel": { + "loggingChannelId": "", + "verboseLevel": 2 } + } } diff --git a/docs/arch/ADR_01.md b/docs/arch/ADR_01.md index d3c1aec..dbfb53f 100644 --- a/docs/arch/ADR_01.md +++ b/docs/arch/ADR_01.md @@ -17,8 +17,8 @@ this should be a fairly pure, from the ground up representation of my plans. I want: -- A way for developers to define and reference dependencies easily, without ever needing to touch the core. -- Useful error messages that are returned when a command is used, that explain what was not available, and (maybe) why +- A way for developers to define and reference dependencies easily, without ever needing to touch the core. +- Useful error messages that are returned when a command is used, that explain what was not available, and (maybe) why A module could have a list of dependencies passed to the constructor, and before execution, the executor can verify that all dependencies are ready to be accessed. If any of the dependencies are not accessible, an error message could returned that's actually helpful, maybe something along the lines of "This command has unmet dependencies: \[deps\]" diff --git a/docs/design.md b/docs/design.md index 6db50bc..203df33 100644 --- a/docs/design.md +++ b/docs/design.md @@ -8,23 +8,23 @@ Because this is being built as a replacement to TechSupportBot, there is some cr Minimum targets include: -- Message logging. This implementation should log messages in a mirrored Logging category. This implementation should handle message events (creation, editing, deletion), and automatically create logging threads that mirror the logged channel. The implementation should handle the massive scale that comes with logging a high volume of messages, and support manual logging, like commands used. -- Factoids. This implementation should include the management of factoids (creation, editing, documenting, and deleting). It should not be bound to a single prefix. -- User moderation. This implementation should include per-channel message logging, as well as an easy framework to moderate users (kick, ban, time out) -- Support for unit tests -- Simple deployment. Deployment should require minimal configuration. a Makefile should be used to simplify processes. +- Message logging. This implementation should log messages in a mirrored Logging category. This implementation should handle message events (creation, editing, deletion), and automatically create logging threads that mirror the logged channel. The implementation should handle the massive scale that comes with logging a high volume of messages, and support manual logging, like commands used. +- Factoids. This implementation should include the management of factoids (creation, editing, documenting, and deleting). It should not be bound to a single prefix. +- User moderation. This implementation should include per-channel message logging, as well as an easy framework to moderate users (kick, ban, time out) +- Support for unit tests +- Simple deployment. Deployment should require minimal configuration. a Makefile should be used to simplify processes. Non-critical high priority targets: -- Google CSE (image search, text search, youtube, pagination) +- Google CSE (image search, text search, youtube, pagination) Non-critical targets: -- Complete extension parity with TechSupportBot +- Complete extension parity with TechSupportBot ## Non-targets -- Multi-server support. This proves to increase configuration, and I feel that the tradeoff of computational power for running multiple instances is worth it. +- Multi-server support. This proves to increase configuration, and I feel that the tradeoff of computational power for running multiple instances is worth it. # Design Choices @@ -41,8 +41,8 @@ TypeScript was chosen to allow for greater resiliency over standard JavaScript. While there are many pros to this decision, there are some drawbacks to be made aware of. -- Resiliency. Javascript and Typescript do not have as robust handling of errors as some alternatives, like Rust. We should try to mitigate this by making use of the `Promise` pattern where possible. -- Performance. Javascript and Typescript are less performant than other possible language choices, however I feel that the performance is still sufficient, and the positives that come with the Discord.js ecosystem outweigh the performance disadvantage. +- Resiliency. Javascript and Typescript do not have as robust handling of errors as some alternatives, like Rust. We should try to mitigate this by making use of the `Promise` pattern where possible. +- Performance. Javascript and Typescript are less performant than other possible language choices, however I feel that the performance is still sufficient, and the positives that come with the Discord.js ecosystem outweigh the performance disadvantage. Kubernetes was chosen because it offers features like Service Discovery and Probing. It would allow for the creation of resiliency clustering. This will introduce initial development complexity, but careful documentation and configuration will mitigate this. The ability to run resiliency nodes on different devices would avoid reliability being dependant on a single server, as this has presented issues in the past. diff --git a/docs/guidelines.md b/docs/guidelines.md index f67f307..3cd73f8 100644 --- a/docs/guidelines.md +++ b/docs/guidelines.md @@ -6,7 +6,7 @@ Please refer to the resources list at the bottom of this section for more commen Code should be thouroughly commented and documented, especially the `core`. Docstrings should be used liberally, and should be in the following locations. -- At the top of files. This docstring should contain a high level summary of the code contained in the file. The below example is for a `ping` module +- At the top of files. This docstring should contain a high level summary of the code contained in the file. The below example is for a `ping` module ```typescript /* @@ -14,9 +14,9 @@ Code should be thouroughly commented and documented, especially the `core`. Docs */ ``` -- Variable definitions. This docstring should at minimum contain a type. -- Class definitions. This docstring should contain a description of the class -- Function definitions. This docstring should explain what the function does, as well as the types of all arguments and return values. +- Variable definitions. This docstring should at minimum contain a type. +- Class definitions. This docstring should contain a description of the class +- Function definitions. This docstring should explain what the function does, as well as the types of all arguments and return values. There are more usecases where docstrings might be used, but they should be used in all the above cases unless you are confident it is not needed. Should you need to add comments later, mark the relevant section with a `TODO` comment. @@ -24,45 +24,45 @@ Comments should adhere to the best practices linked below ## Documentation -- https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html +- https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html ## Best practices -- https://stackoverflow.blog/2021/12/23/best-practices-for-writing-code-comments/ -- https://mitcommlab.mit.edu/broad/commkit/coding-and-comment-style/ +- https://stackoverflow.blog/2021/12/23/best-practices-for-writing-code-comments/ +- https://mitcommlab.mit.edu/broad/commkit/coding-and-comment-style/ # Whitespace ## Spaces -- There should be one space after a comma, none before. A newline may also be used if you are formatting an object or array with JSON style formatting, with a newline after every declaration. -- There should be one space on either side of an assignment operator or boolean comparison. Correct formatting of this will be structured similar to `val = thing`, or `val >= thing`. -- Statements (`if`, `for`, `while`, `function` are to be formatted with a space between the identifier, the arguments, and the context. Correct formatting may look like: +- There should be one space after a comma, none before. A newline may also be used if you are formatting an object or array with JSON style formatting, with a newline after every declaration. +- There should be one space on either side of an assignment operator or boolean comparison. Correct formatting of this will be structured similar to `val = thing`, or `val >= thing`. +- Statements (`if`, `for`, `while`, `function` are to be formatted with a space between the identifier, the arguments, and the context. Correct formatting may look like: ```javascript if (val == thing) { - doTheThing(); + doTheThing(); } ``` ## Tabs -- Tabs are to be composed of 4 spaces. +- Tabs are to be composed of 4 spaces. ## Newlines -- Newlines are to be LF. -- Preferred syntax for files includes two or more newlines after library inclusions, one newline after function declarations, and wherever needed to seperate code into logical sections. +- Newlines are to be LF. +- Preferred syntax for files includes two or more newlines after library inclusions, one newline after function declarations, and wherever needed to seperate code into logical sections. # Variable Names Proper variable naming greatly contributes to the readability and maintainability of code. Names should not be abbreviated in the core, and abbreviation should be kept to a minimum during module development, especially regarding function calls. Some general rules to follow: -- Names should denote single values vs plural values. If a name is denoting type, it's generally not a good variable name. Where a good name for an array of dog names might be `dogs`, a bad name could be `dog` or `dogsArray`. -- Names should generally be pronounceable, if you can't pronounce a variable name, it may not be a good description of the purpose of the variable. -- Don't use different words for the same meaning. Having variables named `parsedData`, `processedDta`, and `parsedData` can lead to confusion, and is an indication of badly named variables. -- Functions should be a verb or verb phrase (generally a verb followed by a noun). +- Names should denote single values vs plural values. If a name is denoting type, it's generally not a good variable name. Where a good name for an array of dog names might be `dogs`, a bad name could be `dog` or `dogsArray`. +- Names should generally be pronounceable, if you can't pronounce a variable name, it may not be a good description of the purpose of the variable. +- Don't use different words for the same meaning. Having variables named `parsedData`, `processedDta`, and `parsedData` can lead to confusion, and is an indication of badly named variables. +- Functions should be a verb or verb phrase (generally a verb followed by a noun). ## Resources diff --git a/jsdoc.json b/jsdoc.json index d53b5f5..e398129 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -1,28 +1,28 @@ { - "source": { - "includePattern": ".+\\.(j|t)s(doc|x)?$", - "include": ["src/core", "README.md", "package.json"] - }, - "plugins": ["plugins/markdown", "better-docs/typescript"], - "opts": { - "encoding": "utf8", - "readme": "./README.md", - "destination": "docs/jsdoc/", - "recurse": true, - "verbose": true, - "template": "./node_modules/clean-jsdoc-theme", - "theme_opts": { - "default_theme": "dark" - } - }, - "templates": { - "cleverLinks": true - }, - "markdown": { - "hardwrap": false, - "idInHeadings": true - }, - "tags": { - "allowUnknownTags": ["optional"] + "source": { + "includePattern": ".+\\.(j|t)s(doc|x)?$", + "include": ["src/core", "README.md", "package.json"] + }, + "plugins": ["plugins/markdown", "better-docs/typescript"], + "opts": { + "encoding": "utf8", + "readme": "./README.md", + "destination": "docs/jsdoc/", + "recurse": true, + "verbose": true, + "template": "./node_modules/clean-jsdoc-theme", + "theme_opts": { + "default_theme": "dark" } + }, + "templates": { + "cleverLinks": true + }, + "markdown": { + "hardwrap": false, + "idInHeadings": true + }, + "tags": { + "allowUnknownTags": ["optional"] + } } diff --git a/k8s/dev.yaml b/k8s/dev.yaml index 686fb50..d8b9fd5 100644 --- a/k8s/dev.yaml +++ b/k8s/dev.yaml @@ -3,72 +3,72 @@ apiVersion: v1 kind: Service metadata: - name: mongodb + name: mongodb spec: - type: ClusterIP - selector: - name: mongodb - ports: - - port: 27017 - targetPort: 27017 + type: ClusterIP + selector: + name: mongodb + ports: + - port: 27017 + targetPort: 27017 --- apiVersion: apps/v1 kind: Deployment metadata: + name: mongodb + labels: name: mongodb - labels: - name: mongodb spec: - selector: - matchLabels: - name: mongodb - template: - metadata: - labels: - name: mongodb - spec: - containers: - - name: mongodb - image: mongo - resources: - limits: - memory: "512Mi" - cpu: "500m" - ports: - - containerPort: 27017 - env: - - name: MONGO_INITDB_ROOT_USERNAME - value: root - - name: MONGO_INITDB_ROOT_PASSWORD - value: root + selector: + matchLabels: + name: mongodb + template: + metadata: + labels: + name: mongodb + spec: + containers: + - name: mongodb + image: mongo + resources: + limits: + memory: '512Mi' + cpu: '500m' + ports: + - containerPort: 27017 + env: + - name: MONGO_INITDB_ROOT_USERNAME + value: root + - name: MONGO_INITDB_ROOT_PASSWORD + value: root --- apiVersion: apps/v1 kind: Deployment metadata: + name: turingbot + labels: name: turingbot - labels: - name: turingbot spec: - replicas: 1 - # Selector and template are basically specifying "when i want to take an action, what do i want to target?" - # in this case, we only want to target *this* container - selector: - matchLabels: - name: turingbot - template: - metadata: - labels: - name: turingbot - spec: - containers: - - name: turingbot - image: turingbot - imagePullPolicy: Never - resources: - limits: - memory: "512Mi" - cpu: "500m" + replicas: 1 + # Selector and template are basically specifying "when i want to take an action, what do i want to target?" + # in this case, we only want to target *this* container + selector: + matchLabels: + name: turingbot + template: + metadata: + labels: + name: turingbot + spec: + containers: + - name: turingbot + image: turingbot + imagePullPolicy: Never + resources: + limits: + memory: '512Mi' + cpu: '500m' # apiVersion: v1 # kind: Service # metadata: diff --git a/package.json b/package.json index 0494931..d9d4f60 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "utf-8-validate": "^6.0.3", "zlib-sync": "^0.1.8" }, - "devDependencies":{ + "devDependencies": { "@types/node": "^20.3.1", "0x": "^5.5.0", "jsdoc": "^4.0.2", diff --git a/src/core/main.ts b/src/core/main.ts index 5c18f89..77690d5 100644 --- a/src/core/main.ts +++ b/src/core/main.ts @@ -166,7 +166,7 @@ function listen(): void { } const foundRootModule = getModWithFirstToken(); - if (foundRootModule == null) { + if (foundRootModule === undefined) { return; } @@ -225,7 +225,7 @@ function listen(): void { .executeCommand(tokens.join(' '), message) .then((value: void | APIEmbed) => { // enable modules to return an embed - if (value !== null) { + if (value !== undefined) { void message.reply({embeds: [value!]}); } }) @@ -303,6 +303,8 @@ function generateHelpMessageForModule( */ async function initializeModules(): Promise { for (const mod of modules) { + // TODO: concurrent-erize this so that more modules are resolved + // while we wait for dep resolution /** If a dependency fails to resolve, this is set to true and the module is not initialized */ const missingDependency = false; // by starting all of the functions at once then awaiting completion, it's considerably more efficient diff --git a/src/core/mongo.ts b/src/core/mongo.ts index 8437940..766abd5 100644 --- a/src/core/mongo.ts +++ b/src/core/mongo.ts @@ -20,7 +20,7 @@ export const mongo = new Dependency('MongoDB', async () => { strict: true, deprecationErrors: true, }, - serverSelectionTimeoutMS: 3000, + serverSelectionTimeoutMS: 15000, }); await mongoClient.connect().catch(err => { diff --git a/src/modules/factoids/factoids.ts b/src/modules/factoids/factoids.ts index b25d7e7..93f6917 100644 --- a/src/modules/factoids/factoids.ts +++ b/src/modules/factoids/factoids.ts @@ -7,7 +7,7 @@ import type {Collection, DeleteResult} from 'mongodb'; import {request} from 'undici'; import * as util from '../../core/util.js'; -import {Attachment, BaseMessageOptions} from 'discord.js'; +import {Attachment, BaseMessageOptions, Events, Message} from 'discord.js'; import {validateMessage} from './factoid_validation.js'; interface Factoid { @@ -20,19 +20,104 @@ interface Factoid { /** The message you'd like to be sent when the factoid is triggered. While preferably an embed, this could be any valid form of message */ message: BaseMessageOptions; } - +/** The name of the MongoDB collection where factoids should be stored */ const FACTOID_COLLECTION_NAME = 'factoids'; - const factoid = new util.RootModule( 'factoid', - 'Simple way to dynamically create and manage factoids (a static message that can be summoned on demand)', + 'Manage or fetch user generated messages', [util.mongo] ); factoid.onInitialize(async () => { - // TODO: add a listener that looks for a factoid if prefix is found + // these are defined outside so that they don't get redefined every time a + // message is sent + const db: Db = util.mongo.fetchValue(); + const factoids: Collection = db.collection( + FACTOID_COLLECTION_NAME + ); + const prefixes: string[] = factoid.config.prefixes; + // listen for a message sent by any of a few prefixes + // only register a listener if at least one prefix was specified + if (prefixes.length === 0) { + return; + } + + util.client.on(Events.MessageCreate, async (message: Message) => { + // anything that does not include + if (!prefixes.includes(message.content.charAt(0))) { + return; + } + // make sure factoids can only be triggered by non bot users + if (message.author.bot) { + return; + } + //remove the prefix, split by spaces, and query the DB + const queryArguments: string[] = message.content.slice(1).split(' '); + const queryResult = await factoids.findOne({ + name: queryArguments[0], + }); + // no match found + if (queryResult === null) { + return; + } + // match found, send factoid + await message.reply(queryResult.message).catch(err => { + util.eventLogger.logEvent( + { + category: util.EventCategory.Error, + location: 'factoid', + description: `An error was encountered sending factoid: ${ + (err as Error).name + }`, + }, + 3 + ); + }); + }); }); +factoid.registerSubModule( + new util.SubModule( + 'get', + 'Fetch a factoid from the database and return it', + async (args, msg) => { + const factoidName: string | undefined = args?.split(' ')[0]; + if (factoidName === '') { + return util.embed.errorEmbed( + 'No factoid name provided, please specify a factoid.' + ); + } + const db: Db = util.mongo.fetchValue(); + const factoids: Collection = db.collection( + FACTOID_COLLECTION_NAME + ); + + // findOne returns null if it doesn't find the thing + const locatedFactoid: Factoid | null = await factoids.findOne({ + name: factoidName, + }); + if (locatedFactoid === null) { + return util.embed.errorEmbed( + 'Unable to located the factoid specified.' + ); + } + + await msg.reply(locatedFactoid.message).catch(err => { + util.eventLogger.logEvent( + { + category: util.EventCategory.Error, + location: 'factoid', + description: `An error was encountered sending factoid: ${ + (err as Error).name + }`, + }, + 3 + ); + }); + } + ) +); + factoid.registerSubModule( new util.SubModule( 'remember', @@ -63,7 +148,7 @@ factoid.registerSubModule( `Factoid validation failed with error: ${(err as Error).name}` ); } - // if any errors were found, return early + // if any errors were found with the factoid to remember, return early if (messageIssues.length > 0) { return util.embed.errorEmbed( `The following issues were found with the attached json (remember cancelled):\n - ${messageIssues.join( @@ -71,21 +156,24 @@ factoid.registerSubModule( )}` ); } - // if no name was specified, return early if (args === undefined) { return util.embed.errorEmbed( 'Factoid name missing from command invocation, please specify a name.' ); } - + // the structure sent to the database const factoid: Factoid = { name: args.split(' ')[0], aliases: [], hidden: false, message: JSON.parse(serializedFactoid), }; - + // strip all mentions from the factoid + // https://discord.com/developers/docs/resources/channel#allowed-mentions-object + factoid.message.allowedMentions = { + parse: [], + }; // TODO: allow plain text factoids by taking everything after the argument // TODO: see if a factoid is stored with the same name @@ -130,42 +218,8 @@ factoid.registerSubModule( ); factoid.registerSubModule( - new util.SubModule( - 'get', - 'Fetch a factoid from the database and return it', - async (args, msg) => { - const factoidName: string | undefined = args?.split(' ')[0]; - if (factoidName === '') { - return util.embed.errorEmbed( - 'No factoid name provided, please specify a factoid.' - ); - } - const db: Db = util.mongo.fetchValue(); - const factoids: Collection = db.collection( - FACTOID_COLLECTION_NAME - ); - - // findOne returns null if it doesn't find the thing - const locatedFactoid: Factoid | null = await factoids.findOne({ - name: factoidName, - }); - if (locatedFactoid === null) { - return util.embed.errorEmbed( - 'Unable to located the factoid specified.' - ); - } - // strip all mentions from the factoid - // https://discord.com/developers/docs/resources/channel#allowed-mentions-object - locatedFactoid.message.allowedMentions = { - parse: [], - }; - - await msg.reply(locatedFactoid.message); - } - ) + new util.SubModule('preview', 'Preview a factoid json without remembering it') ); - -factoid.registerSubModule(new util.SubModule('preview', 'Preview a factoid')); factoid.registerSubModule( new util.SubModule('all', 'Generate a list of all factoids as a webpage') ); diff --git a/tsconfig.json b/tsconfig.json index ecdf5c6..57f37f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { - "compilerOptions": { - "esModuleInterop": true, - "module": "ESNext", - "target": "ESnext", - "lib": ["ESNext"], - - "outDir": "target", - "sourceMap": true, - "moduleResolution": "node", - "incremental": true, - "strict": true - }, - "include": ["src/**/*"] + "compilerOptions": { + "esModuleInterop": true, + "module": "ESNext", + "target": "ESnext", + "lib": ["ESNext"], + + "outDir": "target", + "sourceMap": true, + "moduleResolution": "node", + "incremental": true, + "strict": true + }, + "include": ["src/**/*"] }