diff --git a/00-compile.bat b/00-compile.bat index c9d4954..eeeb264 100644 --- a/00-compile.bat +++ b/00-compile.bat @@ -5,8 +5,8 @@ SETLOCAL echo Compiling Backend REM Add the Node Options for SSL these are requrired since Node v.17 -set NODE_OPTIONS=--openssl-legacy-provider -call $env:NODE_OPTIONS="--openssl-legacy-provider" +REM set NODE_OPTIONS=--openssl-legacy-provider +REM call $env:NODE_OPTIONS="--openssl-legacy-provider" (npm run-script prettier-format) && ( diff --git a/05-link.bat b/05-link.bat index a19faff..8219889 100644 --- a/05-link.bat +++ b/05-link.bat @@ -4,4 +4,32 @@ cd "%DIR%" if not "%1"=="am_admin" (powershell start -verb runas '%0' am_admin & exit /b) -npm link \ No newline at end of file +(npm link) && ( + node contribute/toLinkBrowser.js + + cp ./package.json ./build/package.json + + cd ./build + + (npm link) && ( + cd "%DIR%" + node contribute/toNodejs.js + ) || ( + cd "%DIR%" + node contribute/toNodejs.js + ) +) || ( + node contribute/toLinkBrowser.js + + cp ./package.json ./build/package.json + + cd ./build + + (npm link) && ( + cd "%DIR%" + node contribute/toNodejs.js + ) || ( + cd "%DIR%" + node contribute/toNodejs.js + ) +) \ No newline at end of file diff --git a/contribute/VERSION b/contribute/VERSION index ace256e..321816a 100644 --- a/contribute/VERSION +++ b/contribute/VERSION @@ -1 +1 @@ -1.0.21 \ No newline at end of file +1.0.24 \ No newline at end of file diff --git a/contribute/toLinkBrowser.js b/contribute/toLinkBrowser.js new file mode 100644 index 0000000..d3df479 --- /dev/null +++ b/contribute/toLinkBrowser.js @@ -0,0 +1,28 @@ +/** + * @author Martin Karkowski + * @email m.karkowski@zema.de + * @create date 2022-01-18 13:42:41 + * @modify date 2022-03-18 08:56:20 + * @desc [description] + */ + +const { readFileSync, writeFileSync } = require("fs"); + +const version = readFileSync("./contribute/VERSION", { encoding: "utf-8" }); +const package = JSON.parse(readFileSync("./package.json", { encoding: "utf-8" })); + +package.description = "NoPE Runtime for the Browser"; +delete package.main; +delete package.browser; +delete package.bin; + +package.name = "nope-browser" +package.browser = "nope.js"; +package.main = "nope.js"; +package.version = version; + +package.files = [ + "*", +]; + +writeFileSync("./package.json", JSON.stringify(package, undefined, 2), { encoding: "utf-8" }); \ No newline at end of file diff --git a/helpers/convert_ticks.js b/helpers/convert_ticks.js new file mode 100644 index 0000000..cbc85ae --- /dev/null +++ b/helpers/convert_ticks.js @@ -0,0 +1,27 @@ +/** + * to run the + * https://nodejs.org/en/docs/guides/simple-profiling/ + */ + +const listFiles = require('../dist-nodejs/helpers/fileMethods').listFiles; +const { exec } = require("child_process"); +const { promisify } = require("util") + +const execAsync = promisify(exec); + +async function main() { + const tick_files = await listFiles("./", "-v8.log") + + const promises = []; + + for (const file in tick_files) { + console.log(`converting ${file}`) + promises.push( + execAsync(`node --prof-process ${file} > ${file}.txt`) + ) + } + + await Promise.all(promises) +} + +main().catch(console.error) diff --git a/lib/cli/runNopeBackend.ts b/lib/cli/runNopeBackend.ts index 3573e14..37e1fec 100644 --- a/lib/cli/runNopeBackend.ts +++ b/lib/cli/runNopeBackend.ts @@ -25,6 +25,7 @@ import { generateLogfilePath, useLogFile } from "../logger/fileLogging"; import { getNopeLogger } from "../logger/getLogger"; import { LoggerLevel, LoggerLevels } from "../logger/nopeLogger"; import { setGlobalLoggerLevel } from "../logger/setGlobalLoggerLevel"; +import { recordCPUProfile } from "../profiling/index.nodejs"; import { INopeINopeConnectivityTimeOptions, ValidDefaultSelectors, @@ -60,6 +61,8 @@ export interface RunArgs { forceSelectors: boolean; // The Id to use. id: string; + // Flag to enable profiling. Defaults to false. + profile: boolean; } export const DEFAULT_SETTINGS: RunArgs = { @@ -78,6 +81,7 @@ export const DEFAULT_SETTINGS: RunArgs = { forceUsingSelectors: false, logToFile: false, id: generateId(), + profile: false, }; /** @@ -214,6 +218,13 @@ export async function readInArgs( dest: "communicationLogLevel", }); + parser.add_argument("--profile", { + help: "Flag to enable Profiling", + action: "append", + nargs: "?", + dest: "profile", + }); + const args: RunArgs = parser.parse_args(); if (args.params === "not-provided") { @@ -221,6 +232,7 @@ export async function readInArgs( } args.skipLoadingConfig = Array.isArray(args.skipLoadingConfig); + args.profile = Array.isArray(args.profile); args.logToFile = Array.isArray(args.logToFile); args.forceUsingSelectors = Array.isArray(args.forceUsingSelectors); @@ -243,6 +255,8 @@ export async function runNopeBackend( const args = Object.assign(_defaultSettings, _args); + const closeCallbacks = []; + try { // Try to read in the default config file. opts = JSON.parse( @@ -256,7 +270,7 @@ export async function runNopeBackend( if (args.logToFile) { const fileName = generateLogfilePath("run"); - useLogFile(fileName, 10); + closeCallbacks.push(useLogFile(fileName, 10)); } if (LoggerLevels.includes(args.log)) { @@ -266,6 +280,11 @@ export async function runNopeBackend( // Define a Logger const logger = getNopeLogger("starter"); + if (args.profile) { + logger.warn("Enabled Profiling."); + closeCallbacks.push(recordCPUProfile()); + } + if (!Object.getOwnPropertyNames(validLayers).includes(args.channel)) { logger.error( "Invalid Channel. Please use the following values. " + @@ -286,6 +305,46 @@ export async function runNopeBackend( throw error; } + let _closing = false; + const _dispose = (reason?, p?) => { + if (_closing) { + return; + } + + _closing = true; + + if (reason) { + // If there is a reason + logger.error("Unhandled Rejection at: Promise", p, "reason:", reason); + logger.error(reason); + } else { + // We should close the Process: + logger.warn("received 'ctrl+c'. Shutting down the Instances"); + } + // Exit the Process + const promises = []; + for (const callback of closeCallbacks) { + try { + promises.push(callback()); + } catch (e) { + logger.error("During exiting, an error occourd"); + logger.error(e); + } + } + // Wait for all Promises to finish. + Promise.all(promises).then(() => { + process.exit(); + }); + }; + + // Subscribe to unhandled Reactions. + process.on("unhandledRejection", (reason, p) => _dispose(reason, p)); + process.on("SIGINT", () => _dispose()); + process.on("SIGTERM", () => _dispose()); + process.on("exit", () => { + logger.info("Completed. Goodbye"); + }); + // Assign the Default Setting for the Channel. opts.params = layerDefaultParameters[args.channel]; @@ -336,38 +395,34 @@ export async function runNopeBackend( }, _args.singleton ); + + // Add the Dispatcher + closeCallbacks.push(async () => { + await loader.dispatcher.dispose(); + }); + + // If required load all Packages. + if (!args.skipLoadingConfig) { + // Try to load the Modules. + + if (args.delay > 0) { + logger.info(`Waiting ${args.delay} [s] to get all information.`); + await sleep(args.delay * 1000); + } + + try { + logger.info("loading Packages"); + await loadPackages(loader, args.file, args.delay); + } catch (e) { + logger.error("Unable to load the Packages defined in " + args.file); + } + } } catch (e) { getNopeLogger("cli", "info").error("failed to load the Packages", e); + + throw e; } - // If required load all Packages. - if (!args.skipLoadingConfig) { - // Try to load the Modules. - - if (args.delay > 0) { - logger.info(`Waiting ${args.delay} [s] to get all information.`); - await sleep(args.delay * 1000); - } - - try { - logger.info("loading Packages"); - await loadPackages(loader, args.file, args.delay); - } catch (e) { - logger.error("Unable to load the Packages defined in " + args.file); - } - } - - const _dispose = () => { - // We should close the Process: - logger.warn("received 'ctrl+c'. Shutting down the Instances"); - loader.dispatcher.dispose().finally(process.exit); - }; - - process.on("SIGINT", _dispose); - process.on("SIGTERM", _dispose); - process.on("exit", () => { - logger.info("Completed. Goodbye"); - }); return loader; } @@ -386,16 +441,6 @@ export async function run( forcedArgs: Partial = {}, quite = false ) { - // Subscribe to unhandled Reactions. - process.on("unhandledRejection", (reason, p) => { - console.log("Unhandled Rejection at: Promise", p, "reason:", reason); - console.error(reason); - // application specific logging, throwing an error, or other logic here - - // Forward the error - throw reason; - }); - if (!quite) { console.log(NOPELOGO); console.log("\n\n"); diff --git a/lib/helpers/pathMatchingMethods.ts b/lib/helpers/pathMatchingMethods.ts index 4f39fd5..993558f 100644 --- a/lib/helpers/pathMatchingMethods.ts +++ b/lib/helpers/pathMatchingMethods.ts @@ -7,7 +7,6 @@ */ import { SPLITCHAR } from "./objectMethods"; - export const SEPARATOR = "/"; export const SINGLE_LEVEL_WILDCARD = "+"; export const MULTI_LEVEL_WILDCARD = "#"; diff --git a/lib/logger/fileLogging.ts b/lib/logger/fileLogging.ts index fb55221..867c913 100644 --- a/lib/logger/fileLogging.ts +++ b/lib/logger/fileLogging.ts @@ -10,6 +10,7 @@ import { writeFile } from "fs"; import { join } from "path"; import { createFile } from "../helpers/fileMethods"; import { replaceAll } from "../helpers/stringMethods"; +import { sleep } from "../index.browser"; import { getCentralNopeLogger, getNopeLogger } from "./getLogger"; export const CURRENT_DATE = _parsableISOString(); @@ -45,7 +46,10 @@ export function generateLogfilePath(name: string): string { * @param {number} [bufferSize=0] Default Buffer-Size. If > 0 we will write the log with buffering. * @backend **Only in Nodejs available** */ -export function useLogFile(pathToFile = DEFAULT_FILE, bufferSize = 0): void { +export function useLogFile( + pathToFile = DEFAULT_FILE, + bufferSize = 100 +): () => Promise { const logger = getCentralNopeLogger(); // Define a function, that will write the content of the Buffer to our @@ -128,20 +132,19 @@ export function useLogFile(pathToFile = DEFAULT_FILE, bufferSize = 0): void { } }); - if (bufferSize > 0) { - const clearBufferAtEnd = function () { - consoleLogger.info("Shutdown detected! Trying to Write the Buffer"); + const clearBufferAtEnd = async () => { + consoleLogger.info("Shutdown detected! Trying to Write the Buffer"); - if (readyToWrite) { - // Now if the Data is ready, lets write the - // buffer to the File. - writeBufferToFile(() => process.exit(0)); - } else { - setTimeout(clearBufferAtEnd, 50); - } - }; + while (!readyToWrite) { + await sleep(50); + } - process.on("SIGINT", clearBufferAtEnd); - process.on("SIGTERM", clearBufferAtEnd); - } + const promise = new Promise((resolve, reject) => { + writeBufferToFile(resolve); + }); + + await promise; + }; + + return clearBufferAtEnd; } diff --git a/lib/profiling/index.nodejs.ts b/lib/profiling/index.nodejs.ts new file mode 100644 index 0000000..6a7506d --- /dev/null +++ b/lib/profiling/index.nodejs.ts @@ -0,0 +1,93 @@ +"use strict"; +const fs = require("fs"); +const v8Profiler = require("v8-profiler-next"); + +import { join } from "path"; +import { replaceAll } from "../helpers/stringMethods"; +import { getNopeLogger } from "../index.browser"; +import { createFile } from "../index.nodejs"; + +export const CURRENT_DATE = _parsableISOString(); +export const DEFAULT_LOG_LOCATION = join(process.cwd(), "logs"); +const DEFAULT_FILE = join( + DEFAULT_LOG_LOCATION, + "cpu_profile_" + CURRENT_DATE + ".cpuprofile" +); +const logger = getNopeLogger("CPU-Profiler"); + +function _parsableISOString(date = new Date()) { + let isoString = date.toISOString(); + isoString = replaceAll(isoString, ":", "-"); + isoString = replaceAll(isoString, ".", "-"); + return isoString; +} + +/** + * Generates a Log-File Path based on the given name with the following format: + * /logs/{name}_{date}.log + * + * @export + * @param {string} name Name of the File. + * @return {string} + * @backend **Only in Nodejs available** + */ +export function generateLogfilePath(name: string): string { + return join( + DEFAULT_LOG_LOCATION, + name + "_" + _parsableISOString() + ".cpuprofile" + ); +} + +export function recordCPUProfile(pathToFile = DEFAULT_FILE) { + const title = "cpu-profile"; + + // set generateType 1 to generate new format for cpuprofile + // to be compatible with cpuprofile parsing in vscode. + v8Profiler.setGenerateType(1); + + // ex. 5 mins cpu profile + v8Profiler.startProfiling(title, true); + + let stopped = false; + + const stopProfiling = async () => { + if (stopped) { + return; + } + + stopped = true; + + const profile = v8Profiler.stopProfiling(title); + + const promise = new Promise((resolve, reject) => { + profile.export(function (error, result) { + if (error) { + reject(error); + } + resolve(result); + }); + }); + + const result: any = await promise; + + // if it doesn't have the extension .cpuprofile then + // chrome's profiler tool won't like it. + + // examine the profile: + // Navigate to chrome://inspect + // Click Open dedicated DevTools for Node + // Select the profiler tab + // Load your file + logger.info( + "Please open google chrome and open chrome://inspect and load the file", + pathToFile + ); + + await createFile(pathToFile, result); + + // Clear the Profile. + profile.delete(); + }; + + return stopProfiling; +} diff --git a/lib/pubSub/nopePubSubSystem.ts b/lib/pubSub/nopePubSubSystem.ts index 96356fa..95c43b0 100644 --- a/lib/pubSub/nopePubSubSystem.ts +++ b/lib/pubSub/nopePubSubSystem.ts @@ -1,9 +1,6 @@ /** * @author Martin Karkowski * @email m.karkowski@zema.de - * @create date 2021-11-12 12:25:30 - * @modify date 2022-01-06 09:54:44 - * @desc [description] */ import { memoize } from "lodash"; @@ -45,7 +42,9 @@ type TMatchting = { }; export class PubSubSystemBase< - AD extends IEventAdditionalData = IEventAdditionalData, + AD extends IEventAdditionalData & { + pubSubUpdate?: boolean; + } = IEventAdditionalData, I extends INopeEventEmitter< unknown, unknown, @@ -106,9 +105,12 @@ export class PubSubSystemBase< subTopic: string | false; pubTopic: string | false; callback?: IEventCallback; + observer?: INopeObserver; } >(); + protected _emittersToObservers = new Map(); + protected _matched = new Map(); protected _generateEmitterType: () => I; @@ -183,11 +185,19 @@ export class PubSubSystemBase< // Define a callback, which will be used to forward // the data into the system: + let observer: INopeObserver = undefined; let callback: IEventCallback = undefined; if (pubTopic) { const _this = this; callback = (content, opts) => { + // Internal Data-Update of the pub-sub-system + // we wont push the data again. Otherwise, we + // risk an recursive endloop. + if (opts.pubSubUpdate) { + return; + } + // We use this callback to forward the data into the system: _this._pushData(pubTopic as string, content, opts as AD); }; @@ -199,6 +209,7 @@ export class PubSubSystemBase< pubTopic, subTopic, callback, + observer, }); // Update the Matching Rules. @@ -206,9 +217,12 @@ export class PubSubSystemBase< if (callback) { // If necessary. Add the Callback. - emitter.subscribe(callback, { + observer = emitter.subscribe(callback, { skipCurrent: !this._sendCurrentDataOnSubscription, }); + + // Now lets store our binding. + this._emittersToObservers.set(emitter as unknown as O, observer); } // Now, if required, add the Data to the emitter. @@ -495,7 +509,7 @@ export class PubSubSystemBase< protected _notify( topic: string, options: Partial, - _exclusiveEmitter: O = null + _emitter: O = null ): void { // Check whether a Matching exists for this // Topic, if not add it. @@ -511,10 +525,12 @@ export class PubSubSystemBase< _emitters, ] of referenceToMatch.dataPull.entries()) { for (const _emitter of _emitters) { - // Get a new copy for every element. + // Get a new copy for every emitter. const data = this._pullData(_pathToPull, null); - if (_exclusiveEmitter !== null && _emitter !== _exclusiveEmitter) { + // Only if we want to notify an exclusive emitter we + // have to continue, if our emitter isnt matched. + if (_emitter !== null && _emitter === _emitter) { continue; } // Iterate through all Subscribers @@ -532,7 +548,7 @@ export class PubSubSystemBase< // Get a new copy for every element. const data = this._pullData(_pattern, null); - if (_exclusiveEmitter !== null && _emitter !== _exclusiveEmitter) { + if (_emitter !== null && _emitter !== _emitter) { continue; } // Iterate through all Subscribers @@ -578,6 +594,9 @@ export class PubSubSystemBase< ): void { const _options = this._updateOptions(options); + // Force the Update to be true. + _options.pubSubUpdate = true; + if (containsWildcards(path)) { throw 'The Path contains wildcards. Please use the method "patternbasedPullData" instead'; } else if (path === "") { diff --git a/lib/types/nope/nopePubSub.interface.ts b/lib/types/nope/nopePubSub.interface.ts index 9db22db..471da4e 100644 --- a/lib/types/nope/nopePubSub.interface.ts +++ b/lib/types/nope/nopePubSub.interface.ts @@ -1,9 +1,6 @@ /** * @author Martin Karkowski * @email m.karkowski@zema.de - * @create date 2021-11-12 17:33:12 - * @modify date 2022-01-06 08:32:34 - * @desc [description] */ import { INopeDescriptor } from "./nopeDescriptor.interface"; @@ -22,6 +19,9 @@ export interface ITopicSetContentOptions extends IEventAdditionalData { topic: string; } +/** + * The Topic Type. + */ export type INopeTopic = INopeEventEmitter< T, S, diff --git a/package-lock.json b/package-lock.json index 00c1e62..aa7e056 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nope", - "version": "1.0.17", + "version": "1.0.24", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nope", - "version": "1.0.17", + "version": "1.0.24", "license": "MIT", "dependencies": { "argparse": "^2.0.1", @@ -33,6 +33,7 @@ "ts-morph": "^13.0.2", "typescript-json-schema": "^0.52.0", "uuid": "^8.3.2", + "v8-profiler-next": "^1.5.1", "websocket-stream": "^5.5.2" }, "bin": { @@ -6620,9 +6621,7 @@ "node_modules/nan": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true, - "optional": true + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" }, "node_modules/nanomatch": { "version": "1.2.13", @@ -17240,6 +17239,15 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-profiler-next": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/v8-profiler-next/-/v8-profiler-next-1.5.1.tgz", + "integrity": "sha512-7gnfJ3x7zN3gzmVs69OvJBNze5dLCIeY2Necy+IzomWmprSCRsBbCn5GfwAJHkWJxzoex3gyRexxvYGlgR93yg==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.1" + } + }, "node_modules/validate-npm-package-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", @@ -23506,9 +23514,7 @@ "nan": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", - "dev": true, - "optional": true + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" }, "nanomatch": { "version": "1.2.13", @@ -31533,6 +31539,14 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-profiler-next": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/v8-profiler-next/-/v8-profiler-next-1.5.1.tgz", + "integrity": "sha512-7gnfJ3x7zN3gzmVs69OvJBNze5dLCIeY2Necy+IzomWmprSCRsBbCn5GfwAJHkWJxzoex3gyRexxvYGlgR93yg==", + "requires": { + "nan": "^2.14.1" + } + }, "validate-npm-package-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", diff --git a/package.json b/package.json index b6d865b..b2ab911 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nope", - "version": "1.0.21", + "version": "1.0.24", "description": "NoPE Runtime for Nodejs. For Browser-Support please use nope-browser", "files": [ "dist-nodejs/**/*", @@ -63,6 +63,7 @@ "ts-morph": "^13.0.2", "typescript-json-schema": "^0.52.0", "uuid": "^8.3.2", + "v8-profiler-next": "^1.5.1", "websocket-stream": "^5.5.2" }, "devDependencies": { diff --git a/package.json.bak b/package.json.bak deleted file mode 100644 index 56927fb..0000000 --- a/package.json.bak +++ /dev/null @@ -1,99 +0,0 @@ -{ - "name": "nope", - "version": "1.0.3", - "description": "NoPE Runtime for Nodejs. For Browser-Support please use nope-browser", - "browser": "build/nope.js", - "main": "dist-nodejs/index.nodejs.js", - "files": [ - "build/**/*", - "dist-nodejs/**/*", - "lib/**/*", - "bin/*" - ], - "bin": { - "nope-js": "./bin/nope" - }, - "scripts": { - "test": "mocha", - "compile-nodejs": "tsc -p ./tsconfig.json", - "compile": "tsc -p ./tsconfig.browser.json", - "build": "npx webpack -c webpack-typescript.config.js", - "doc": "npx jsdoc ./dist/**/* -d docs", - "dev": "NODE_OPTIONS='--inspect' next dev", - "start": "node ./dist/lib/cli/nope.js", - "prettier-format": "run-script-os", - "prettier-format:win32": "prettier \"./lib/**/*.ts\" --write", - "prettier-format:darwin:linux": "prettier 'lib/**/*.ts' --write", - "prettier-format:default": "prettier 'lib/**/*.ts' --write", - "prettier-watch": "run-script-os", - "prettier-watch:win32": "onchange \"lib/**/*.ts\" -- prettier --write {{changed}}", - "prettier-watch:darwin:linux": "onchange 'lib/**/*.ts' -- prettier --write {{changed}}", - "prettier-watch:default": "onchange 'lib/**/*.ts' -- prettier --write {{changed}}" - }, - "mocha": { - "reporter": "spec", - "spec": "dist/**/*.spec.js" - }, - "repository": { - "type": "git", - "url": "git+https://git.zema.de/tfs/ZISS/_git/nope-js" - }, - "keywords": [], - "author": "Martin Karkowski", - "license": "MIT", - "bugs": { - "url": "https://git.zema.de/tfs/ZISS/_git/nope-js/issues" - }, - "homepage": "https://git.zema.de/tfs/ZISS/_git/nope-js#readme", - "dependencies": { - "async": "^3.2.2", - "comment-parser": "^1.3.0", - "cors": "^2.8.5", - "handlebars": "^4.7.7", - "inquirer": "^8.2.0", - "inquirer-fuzzy-path": "^2.3.0", - "inquirer-search-list": "^1.2.6", - "inversify": "^6.0.1", - "js-logger": "^1.6.1", - "lodash": "^4.17.21", - "mathjs": "^10.0.2", - "mqtt": "^4.3.4", - "mqtt-pattern": "^1.2.0", - "next": "^12.0.7", - "npm": "^8.3.0", - "npx": "^10.2.2", - "reflect-metadata": "^0.1.13", - "run-script-os": "^1.1.6", - "rxjs": "^7.5.1", - "socket.io": "^4.4.1", - "socket.io-client": "^4.4.1", - "ts-morph": "^13.0.2", - "typescript-json-schema": "^0.52.0", - "uuid": "^8.3.2", - "websocket-stream": "^5.5.2" - }, - "devDependencies": { - "@babel/preset-typescript": "^7.16.7", - "@types/async": "^3.2.12", - "@types/chai": "^4.3.0", - "@types/lodash": "^4.14.178", - "@types/node": "^17.0.8", - "@types/socket.io": "^3.0.1", - "@types/socket.io-client": "^1.4.36", - "chai": "^4.3.4", - "dts-bundle": "^0.7.3", - "dts-bundle-webpack": "^1.0.2", - "eslint": "^8.6.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-prettier": "^4.0.0", - "mocha": "^9.1.3", - "npm-check-updates": "^12.1.0", - "onchange": "^7.1.0", - "prettier": "2.5.1", - "typedoc": "^0.22.10", - "typescript": "^4.5.4", - "webpack": "^4.46.0", - "webpack-cli": "^4.8.0" - } -} \ No newline at end of file