From 7798e36458c7f555d2bcb8a5857a6db47b7211da Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 29 Jul 2023 17:10:19 -0500 Subject: [PATCH] feat!: v3 (#294) * refactor: move things to core, imports not fixed yet * work on strategy and lifted Context * remove id from lifted Context * refactor: remove dependence on discord.js for module stoore * moving and fixing imports * chore: move operators into core * chore: fix paths * add wrapper platform field * add deprecation warning * chore:update paths * chore:remove const function * chore: remove deprecated symbols * docs: add documentation to internal function * chore: remove deprecated support for plugins * chore: remove dependence on discord.js Awaitable type * chore: update typings * lift requiredDependencyKeys out of makeFetcher * move strategy to index.ts and add adapters * chore: fix typings * chore: move command args matrix as binding * feat: make Context platform specific, CoreContext as Core * chore: remove extra file * chore: move prettier into package.json * chore(core): update imports and operators * chore(core): add DefaultWrapper as sern classic * move eslint and prettier configs to json * chore: remove utils folder in favor of single file * chore: remove redundant directories for single files * chore: remove redundant directories for single files * refactor: move and update things * chore: move commands into seperate file * chore: serverless work * chore: remove redundant directories for single files * chore: rename, wip refactoring * chore: redundant directory * refactor: internalize operators * feat!: new module resolution algorithm * chore: refactor and move things * chore: refactor and add multiplatform typings * chore: remove leaky import * chore: add agnostic predicates * chore: add old context here until i figure out what to do * chore: update Proccessed typing to ./core * chore: add tweetnacl * revert: multiplatform * revert: multiplatform * chore: modularize and split typings * chore: revert multiplatform * chore: revert multi and mov sernEmitter * chore: revert multi and clean up code * refactor: add createGenericHandler * refactor: remove unneeded signatures and fix imports * feat: add getPublishableCommands to ModuleManager * chore: remove bad imports * style: pretty * revert: remove AnyDependencies type * refactor: fold switch case * docs: specifics * chore: change all file names to camel case * refactor: change all files to camelcase and refactor * revert: remove cloudflare typings * feat: SernEmitter now captures promise rejections * chore: fix InitArgs missing * chore: move typings * chore: move and clean * chore: delete plugins dir * chore: cleanup dispatchers subdirectory for single file * chore: move context into structures directory * refactor: cleaning up code and renaming variables * chore: update name of function to reflect use * revert: multiple entry points * revert: readd discordEvent * refactor: rename, format, move things * feat: types organization and cleaning up code base * fix: unaliased modules would throw error * build: speed up build * revert: readd module store and add contract * add separate id for id processing * chore: progress of globalizing dependencies type * chore: update container and init hook progress * style: format & lint * feat: dev and prod mode * fix: directories ignoring incorrectly * refactor: move metadata outside of module declarations * revert: re export command executable and event executable * refactor: a lot * fix: plugins for class modules and module loader * style: pretty * fix class based module loading * feat: globalize dependencies type * revert: internal name * feat: add new sern emitter event * refactor: remove cast * refactor: add better typings for sern event modules * test: add tests * test: add more tests * feat: change error handling contract * chore: make changes in codebase after error contract change * docs: add purpose of d.ts file * revert removal of crash method and mark deprecated * fix: typings for options- have access to all properties now * refactor: npx knip * 3.0.0-rc1 * chore: fix for version 3 and reexport old types * fix: reexport payload and button modules * fix: component commands incorrectly aligned and ordered * chore: bump version * test: add id generation testing * refactor: algorithm for module resolution * chore: bump vers * test: add eventDispatcher test * *.test.ts * fix: autocomplete nested option * chore: bump vers * add npmignore .yarn * feat: experimental loading sern.config.json * refactor: simplify build * chore: bump vers * chore: add documentation for service api * add since * feat: add possible mode option in file loading mode * refactor: remove two unneeded functions and refactor to throw early * refactor: clean up handler code * fix: undefined this binding * refactor: clean up signatures and types * refactor: make evident the internal api and move around stuff * refactor: remove circular dependencies * fix circulars and imports * oops, moving around mroe stuff * refresh lock * chore: import type and prettier * style: prettier * feat: solidify init logic * fix module-loading.ts --------- Co-authored-by: jacoobes --- .dependency-cruiser.js | 450 ++++ .eslintrc | 13 - .npmignore | 5 +- .prettierrc | 8 - dependency-graph.svg | 1484 +++++++++++++ package.json | 70 +- src/core/_internal.ts | 9 + src/core/contracts/emitter.ts | 7 + src/core/contracts/error-handling.ts | 21 + src/core/contracts/index.ts | 6 + src/core/contracts/init.ts | 9 + src/core/contracts/logging.ts | 11 + src/core/contracts/module-manager.ts | 22 + src/core/contracts/module-store.ts | 9 + .../create-plugins.ts} | 16 +- src/core/functions.ts | 83 + src/core/id.ts | 63 + src/core/index.ts | 4 + src/core/ioc/base.ts | 34 + src/core/ioc/container.ts | 67 + src/core/ioc/dependency-injection.ts | 90 + src/core/ioc/index.ts | 2 + src/core/module-loading.ts | 141 ++ src/core/modules.ts | 112 + src/core/operators.ts | 71 + src/core/predicates.ts | 34 + src/{handler => core}/structures/context.ts | 68 +- src/core/structures/core-context.ts | 32 + src/{handler => core}/structures/enums.ts | 98 +- src/core/structures/index.ts | 5 + src/core/structures/module-store.ts | 12 + .../structures/sern-emitter.ts} | 15 +- .../structures/services/error-handling.ts | 21 + src/core/structures/services/index.ts | 3 + .../structures/services/logger.ts} | 14 +- .../structures/services/module-manager.ts | 50 + src/handler/contracts/errorHandling.ts | 50 - src/handler/contracts/index.ts | 3 - src/handler/contracts/moduleManager.ts | 27 - src/handler/dependencies/index.ts | 2 - src/handler/dependencies/lifetimeFunctions.ts | 63 - src/handler/dependencies/provider.ts | 86 - src/handler/events/dispatchers/dispatchers.ts | 67 - src/handler/events/dispatchers/provideArgs.ts | 23 - src/handler/events/interactionHandler.ts | 118 - src/handler/events/messageHandler.ts | 92 - src/handler/events/observableHandling.ts | 121 -- src/handler/events/operators/index.ts | 82 - src/handler/events/readyHandler.ts | 106 - .../events/userDefinedEventsHandling.ts | 74 - src/handler/module-loading/readFile.ts | 64 - src/handler/plugins/index.ts | 2 - src/handler/sern.ts | 138 -- src/handler/structures/errors.ts | 38 - src/handler/structures/index.ts | 6 - src/handler/structures/moduleStore.ts | 27 - src/handler/structures/wrapper.ts | 20 - src/handler/utilities/functions.ts | 36 - src/handler/utilities/treeSearch.ts | 54 - .../index.ts => handlers/_internal.ts} | 2 +- src/handlers/dispatchers.ts | 117 + src/handlers/event-utils.ts | 237 +++ src/handlers/interaction-event.ts | 31 + src/handlers/message-event.ts | 46 + src/handlers/ready-event.ts | 48 + src/handlers/user-defined-events.ts | 37 + src/index.ts | 63 +- src/sern.ts | 75 + src/types/{module.ts => core-modules.ts} | 140 +- .../plugins/args.ts => types/core-plugin.ts} | 102 +- src/types/core.ts | 21 + src/types/dependencies.d.ts | 12 + src/types/handler.ts | 71 - src/types/ioc.ts | 48 + src/types/plugin.ts | 74 - src/types/utility.ts | 29 + test/core/contracts.test.ts | 16 + test/core/create-plugin.test.ts | 32 + test/core/functions.test.ts | 404 ++++ test/core/ioc.test.ts | 52 + test/core/services.test.ts | 77 + test/handlers/dispatchers.test.ts | 45 + test/handlers/id.test.ts | 72 + tsconfig-base.json | 18 - tsconfig-cjs.json | 8 - tsconfig-esm.json | 8 - tsconfig.json | 20 +- tsup.config.js | 73 +- yarn.lock | 1889 ++++++++++++++--- 89 files changed, 6229 insertions(+), 2096 deletions(-) create mode 100644 .dependency-cruiser.js delete mode 100644 .eslintrc delete mode 100644 .prettierrc create mode 100644 dependency-graph.svg create mode 100644 src/core/_internal.ts create mode 100644 src/core/contracts/emitter.ts create mode 100644 src/core/contracts/error-handling.ts create mode 100644 src/core/contracts/index.ts create mode 100644 src/core/contracts/init.ts create mode 100644 src/core/contracts/logging.ts create mode 100644 src/core/contracts/module-manager.ts create mode 100644 src/core/contracts/module-store.ts rename src/{handler/plugins/createPlugin.ts => core/create-plugins.ts} (84%) create mode 100644 src/core/functions.ts create mode 100644 src/core/id.ts create mode 100644 src/core/index.ts create mode 100644 src/core/ioc/base.ts create mode 100644 src/core/ioc/container.ts create mode 100644 src/core/ioc/dependency-injection.ts create mode 100644 src/core/ioc/index.ts create mode 100644 src/core/module-loading.ts create mode 100644 src/core/modules.ts create mode 100644 src/core/operators.ts create mode 100644 src/core/predicates.ts rename src/{handler => core}/structures/context.ts (54%) create mode 100644 src/core/structures/core-context.ts rename src/{handler => core}/structures/enums.ts (58%) create mode 100644 src/core/structures/index.ts create mode 100644 src/core/structures/module-store.ts rename src/{handler/sernEmitter.ts => core/structures/sern-emitter.ts} (88%) create mode 100644 src/core/structures/services/error-handling.ts create mode 100644 src/core/structures/services/index.ts rename src/{handler/contracts/logging.ts => core/structures/services/logger.ts} (68%) create mode 100644 src/core/structures/services/module-manager.ts delete mode 100644 src/handler/contracts/errorHandling.ts delete mode 100644 src/handler/contracts/index.ts delete mode 100644 src/handler/contracts/moduleManager.ts delete mode 100644 src/handler/dependencies/index.ts delete mode 100644 src/handler/dependencies/lifetimeFunctions.ts delete mode 100644 src/handler/dependencies/provider.ts delete mode 100644 src/handler/events/dispatchers/dispatchers.ts delete mode 100644 src/handler/events/dispatchers/provideArgs.ts delete mode 100644 src/handler/events/interactionHandler.ts delete mode 100644 src/handler/events/messageHandler.ts delete mode 100644 src/handler/events/observableHandling.ts delete mode 100644 src/handler/events/operators/index.ts delete mode 100644 src/handler/events/readyHandler.ts delete mode 100644 src/handler/events/userDefinedEventsHandling.ts delete mode 100644 src/handler/module-loading/readFile.ts delete mode 100644 src/handler/plugins/index.ts delete mode 100644 src/handler/sern.ts delete mode 100644 src/handler/structures/errors.ts delete mode 100644 src/handler/structures/index.ts delete mode 100644 src/handler/structures/moduleStore.ts delete mode 100644 src/handler/structures/wrapper.ts delete mode 100644 src/handler/utilities/functions.ts delete mode 100644 src/handler/utilities/treeSearch.ts rename src/{handler/events/dispatchers/index.ts => handlers/_internal.ts} (50%) create mode 100644 src/handlers/dispatchers.ts create mode 100644 src/handlers/event-utils.ts create mode 100644 src/handlers/interaction-event.ts create mode 100644 src/handlers/message-event.ts create mode 100644 src/handlers/ready-event.ts create mode 100644 src/handlers/user-defined-events.ts create mode 100644 src/sern.ts rename src/types/{module.ts => core-modules.ts} (76%) rename src/{handler/plugins/args.ts => types/core-plugin.ts} (69%) create mode 100644 src/types/core.ts create mode 100644 src/types/dependencies.d.ts delete mode 100644 src/types/handler.ts create mode 100644 src/types/ioc.ts delete mode 100644 src/types/plugin.ts create mode 100644 src/types/utility.ts create mode 100644 test/core/contracts.test.ts create mode 100644 test/core/create-plugin.test.ts create mode 100644 test/core/functions.test.ts create mode 100644 test/core/ioc.test.ts create mode 100644 test/core/services.test.ts create mode 100644 test/handlers/dispatchers.test.ts create mode 100644 test/handlers/id.test.ts delete mode 100644 tsconfig-base.json delete mode 100644 tsconfig-cjs.json delete mode 100644 tsconfig-esm.json diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js new file mode 100644 index 0000000..076dad5 --- /dev/null +++ b/.dependency-cruiser.js @@ -0,0 +1,450 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +module.exports = { + forbidden: [ + /* rules from the 'recommended' preset: */ + { + name: 'no-circular', + severity: 'warn', + comment: + 'This dependency is part of a circular relationship. You might want to revise ' + + 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + from: {}, + to: { + circular: true, + }, + }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + 'add an exception for it in your dependency-cruiser configuration. By default ' + + 'this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration ' + + 'files (.d.ts), tsconfig.json and some of the babel and webpack configs.', + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$', // dot files + '\\.d\\.ts$', // TypeScript declaration files + '(^|/)tsconfig\\.json$', // TypeScript config + '(^|/)(babel|webpack)\\.config\\.(js|cjs|mjs|ts|json)$', // other configs + ], + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['core'], + path: [ + '^(v8/tools/codemap)$', + '^(v8/tools/consarray)$', + '^(v8/tools/csvparser)$', + '^(v8/tools/logreader)$', + '^(v8/tools/profile_view)$', + '^(v8/tools/profile)$', + '^(v8/tools/SourceMap)$', + '^(v8/tools/splaytree)$', + '^(v8/tools/tickprocessor-driver)$', + '^(v8/tools/tickprocessor)$', + '^(node-inspect/lib/_inspect)$', + '^(node-inspect/lib/internal/inspect_client)$', + '^(node-inspect/lib/internal/inspect_repl)$', + '^(async_hooks)$', + '^(punycode)$', + '^(domain)$', + '^(constants)$', + '^(sys)$', + '^(_linklist)$', + '^(_stream_wrap)$', + ], + }, + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['deprecated'], + }, + }, + { + name: 'no-non-package-json', + severity: 'error', + comment: + "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + 'available on live with an non-guaranteed version. Fix it by adding the package to the dependencies ' + + 'in your package.json.', + from: {}, + to: { + dependencyTypes: ['npm-no-pkg', 'npm-unknown'], + }, + }, + { + name: 'not-to-unresolvable', + comment: + "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + 'module: add it to your package.json. In all other cases you likely already know what to do.', + severity: 'error', + from: {}, + to: { + couldNotResolve: true, + }, + }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + 'in your package.json i.e. bot as a devDependencies and in dependencies. This will cause ' + + 'maintenance problems later on.', + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ['type-only'], + }, + }, + + /* rules you might want to tweak for your specific situation: */ + { + name: 'not-to-test', + comment: + "This module depends on code within a folder that should only contain tests. As tests don't " + + "implement functionality this is odd. Either you're writing a test outside the test folder " + + "or there's something in the test folder that isn't a test.", + severity: 'error', + from: { + pathNot: '^(test)', + }, + to: { + path: '^(test)', + }, + }, + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '\\.(spec|test)\\.(js|mjs|cjs|ts|ls|coffee|litcoffee|coffee\\.md)$', + }, + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(src)', + pathNot: '\\.(spec|test)\\.(js|mjs|cjs|ts|ls|coffee|litcoffee|coffee\\.md)$', + }, + to: { + dependencyTypes: ['npm-dev'], + }, + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + 'This module depends on an npm package that is declared as an optional dependency ' + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + 'dependency-cruiser configuration.', + from: {}, + to: { + dependencyTypes: ['npm-optional'], + }, + }, + { + name: 'peer-deps-used', + comment: + 'This module depends on an npm package that is declared as a peer dependency ' + + 'in your package.json. This makes sense if your package is e.g. a plugin, but in ' + + 'other cases - maybe not so much. If the use of a peer dependency is intentional ' + + 'add an exception to your dependency-cruiser configuration.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: ['npm-peer'], + }, + }, + ], + options: { + /* conditions specifying which files not to follow further when encountered: + - path: a regular expression to match + - dependencyTypes: see https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-reference.md#dependencytypes-and-dependencytypesnot + for a complete list + */ + doNotFollow: { + path: 'node_modules', + }, + + /* conditions specifying which dependencies to exclude + - path: a regular expression to match + - dynamic: a boolean indicating whether to ignore dynamic (true) or static (false) dependencies. + leave out if you want to exclude neither (recommended!) + */ + // exclude : { + // path: '', + // dynamic: true + // }, + + /* pattern specifying which files to include (regular expression) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : '', + + /* dependency-cruiser will include modules matching against the focus + regular expression in its output, as well as their neighbours (direct + dependencies and dependents) + */ + // focus : '', + + /* list of module systems to cruise */ + // moduleSystems: ['amd', 'cjs', 'es6', 'tsd'], + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/develop/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: '', + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + tsPreCompilationDeps: true, + + /* + list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + tsConfig: { + fileName: 'tsconfig.json', + }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters to be passed if + your webpack config is a function and takes them (see webpack documentation + for details) + */ + // webpackConfig: { + // fileName: './webpack.config.js', + // env: {}, + // arguments: {}, + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation (and whatever other naughty things babel plugins do to + source code). This feature is well tested and usable, but might change + behavior a bit over time (e.g. more precise results for used module + systems) without dependency-cruiser getting a major version bump. + */ + // babelConfig: { + // fileName: './.babelrc' + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. You can set most of these + options in a webpack.conf.js - this section is here for those + projects that don't have a separate webpack config file. + + Note: settings in webpack.conf.js override the ones specified here. + */ + enhancedResolveOptions: { + /* List of strings to consider as 'exports' fields in package.json. Use + ['exports'] when you use packages that use such a field and your environment + supports it (e.g. node ^12.19 || >=14.7 or recent versions of webpack). + + If you have an `exportsFields` attribute in your webpack config, that one + will have precedence over the one specified here. + */ + exportsFields: ['exports'], + /* List of conditions to check for in the exports field. e.g. use ['imports'] + if you're only interested in exposed es6 modules, ['require'] for commonjs, + or all conditions at once `(['import', 'require', 'node', 'default']`) + if anything goes for you. Only works when the 'exportsFields' array is + non-empty. + + If you have a 'conditionNames' attribute in your webpack config, that one will + have precedence over the one specified here. + */ + conditionNames: ['import', 'require', 'node', 'default'], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment. If that list is larger than what you need (e.g. + it contains .js, .jsx, .ts, .tsx, .cts, .mts - but you don't use + TypeScript you can pass just the extensions you actually use (e.g. + [".js", ".jsx"]). This can speed up the most expensive step in + dependency cruising (module resolution) quite a bit. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* + If your TypeScript project makes use of types specified in 'types' + fields in package.jsons of external dependencies, specify "types" + in addition to "main" in here, so enhanced-resolve (the resolver + dependency-cruiser uses) knows to also look there. You can also do + this if you're not sure, but still use TypeScript. In a future version + of dependency-cruiser this will likely become the default. + */ + mainFields: ['main', 'types'], + }, + reporterOptions: { + dot: { + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but not the innards your app depends upon. + */ + collapsePattern: 'node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + don't worry - dependency-cruiser will fall back to the default one. + */ + // theme: { + // graph: { + // /* use splines: "ortho" for straight lines. Be aware though + // graphviz might take a long time calculating ortho(gonal) + // routings. + // */ + // splines: "true" + // }, + // modules: [ + // { + // criteria: { matchesFocus: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesFocus: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { matchesReaches: true }, + // attributes: { + // fillcolor: "lime", + // penwidth: 2, + // }, + // }, + // { + // criteria: { matchesReaches: false }, + // attributes: { + // fillcolor: "lightgrey", + // }, + // }, + // { + // criteria: { source: "^src/model" }, + // attributes: { fillcolor: "#ccccff" } + // }, + // { + // criteria: { source: "^src/view" }, + // attributes: { fillcolor: "#ccffcc" } + // }, + // ], + // dependencies: [ + // { + // criteria: { "rules[0].severity": "error" }, + // attributes: { fontcolor: "red", color: "red" } + // }, + // { + // criteria: { "rules[0].severity": "warn" }, + // attributes: { fontcolor: "orange", color: "orange" } + // }, + // { + // criteria: { "rules[0].severity": "info" }, + // attributes: { fontcolor: "blue", color: "blue" } + // }, + // { + // criteria: { resolved: "^src/model" }, + // attributes: { color: "#0000ff77" } + // }, + // { + // criteria: { resolved: "^src/view" }, + // attributes: { color: "#00770077" } + // } + // ] + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: + '^(packages|src|lib|app|bin|test(s?)|spec(s?))/[^/]+|node_modules/(@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + for 'archi' dependency-cruiser will use the one specified in the + dot section (see above), if any, and otherwise use the default one. + */ + // theme: { + // }, + }, + text: { + highlightFocused: true, + }, + }, + }, +}; +// generated: dependency-cruiser@13.0.5 on 2023-07-08T03:48:00.632Z diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index b395b17..0000000 --- a/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "parser": "@typescript-eslint/parser", - "extends": ["plugin:@typescript-eslint/recommended"], - "parserOptions": { "ecmaVersion": "latest", "sourceType": "script" }, - "rules": { - "@typescript-eslint/no-non-null-assertion": "off", - "quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }], - "semi": ["error", "always"], - "@typescript-eslint/no-empty-interface": 0, - "@typescript-eslint/ban-types": 0, - "@typescript-eslint/no-explicit-any": "off" - } -} diff --git a/.npmignore b/.npmignore index d9da6cb..243080d 100644 --- a/.npmignore +++ b/.npmignore @@ -8,6 +8,7 @@ logs npm-debug.log* yarn-debug.log* yarn-error.log* +.yarn # Runtime data pids *.pid @@ -80,9 +81,7 @@ typings/ # FuseBox cache .fusebox/ -# TypeScript build output -dist - +test # VisualStudio Config file .vs diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 78e2e0b..0000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 100, - "tabWidth": 4, - "arrowParens": "avoid" -} diff --git a/dependency-graph.svg b/dependency-graph.svg new file mode 100644 index 0000000..2ae619b --- /dev/null +++ b/dependency-graph.svg @@ -0,0 +1,1484 @@ + + + + + + +dependency-cruiser output + + +cluster_src + +src + + +cluster_src/core + +core + + +cluster_src/core/contracts + +contracts + + +cluster_src/core/ioc + +ioc + + +cluster_src/core/structures + +structures + + +cluster_src/core/structures/services + +services + + +cluster_src/handlers + +handlers + + +cluster_src/types + +types + + + +src/core/_internal.ts + + +_internal.ts + + + + + +src/types/core-plugin.ts + + +core-plugin.ts + + + + + +src/core/_internal.ts->src/types/core-plugin.ts + + + + + +no-circular + + + +src/core/functions.ts + + +functions.ts + + + + + +src/core/_internal.ts->src/core/functions.ts + + + + + +no-circular + + + +src/core/id.ts + + +id.ts + + + + + +src/core/_internal.ts->src/core/id.ts + + + + + +no-circular + + + +src/core/module-loading.ts + + +module-loading.ts + + + + + +src/core/_internal.ts->src/core/module-loading.ts + + + + + +no-circular + + + +src/core/operators.ts + + +operators.ts + + + + + +src/core/_internal.ts->src/core/operators.ts + + + + + +no-circular + + + +src/core/predicates.ts + + +predicates.ts + + + + + +src/core/_internal.ts->src/core/predicates.ts + + + + + +src/core/structures/enums.ts + + +enums.ts + + + + + +src/core/_internal.ts->src/core/structures/enums.ts + + + + + +src/core/structures/module-store.ts + + +module-store.ts + + + + + +src/core/_internal.ts->src/core/structures/module-store.ts + + + + + +no-circular + + + +src/core/structures/services/index.ts + + +index.ts + + + + + +src/core/_internal.ts->src/core/structures/services/index.ts + + + + + +no-circular + + + +src/types/utility.ts + + +utility.ts + + + + + +src/types/core-plugin.ts->src/types/utility.ts + + + + + +no-circular + + + +src/types/core-modules.ts + + +core-modules.ts + + + + + +src/types/core-plugin.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/core/index.ts + + +index.ts + + + + + +src/types/core-plugin.ts->src/core/index.ts + + + + + +no-circular + + + +src/core/functions.ts->src/types/core-plugin.ts + + + + + +no-circular + + + +src/core/functions.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/core/structures/index.ts + + +index.ts + + + + + +src/core/functions.ts->src/core/structures/index.ts + + + + + +no-circular + + + +src/core/id.ts->src/core/structures/index.ts + + + + + +no-circular + + + +src/core/module-loading.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/types/core.ts + + +core.ts + + + + + +src/core/module-loading.ts->src/types/core.ts + + + + + +src/core/operators.ts->src/types/core-plugin.ts + + + + + +no-circular + + + +src/core/contracts/index.ts + + +index.ts + + + + + +src/core/operators.ts->src/core/contracts/index.ts + + + + + +src/core/structures/module-store.ts->src/core/contracts/index.ts + + + + + +src/core/structures/module-store.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/core/structures/services/error-handling.ts + + +error-handling.ts + + + + + +src/core/structures/services/index.ts->src/core/structures/services/error-handling.ts + + + + + +no-circular + + + +src/core/structures/services/logger.ts + + +logger.ts + + + + + +src/core/structures/services/index.ts->src/core/structures/services/logger.ts + + + + + +no-circular + + + +src/core/structures/services/module-manager.ts + + +module-manager.ts + + + + + +src/core/structures/services/index.ts->src/core/structures/services/module-manager.ts + + + + + +no-circular + + + +src/core/contracts/emitter.ts + + +emitter.ts + + + + + +src/core/contracts/emitter.ts->src/types/utility.ts + + + + + +no-circular + + + +src/types/utility.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/types/utility.ts->src/core/index.ts + + + + + +no-circular + + + +src/core/contracts/error-handling.ts + + +error-handling.ts + + + + + +src/core/contracts/index.ts->src/core/contracts/emitter.ts + + + + + +no-circular + + + +src/core/contracts/index.ts->src/core/contracts/error-handling.ts + + + + + +src/core/contracts/init.ts + + +init.ts + + + + + +src/core/contracts/index.ts->src/core/contracts/init.ts + + + + + +no-circular + + + +src/core/contracts/logging.ts + + +logging.ts + + + + + +src/core/contracts/index.ts->src/core/contracts/logging.ts + + + + + +src/core/contracts/module-manager.ts + + +module-manager.ts + + + + + +src/core/contracts/index.ts->src/core/contracts/module-manager.ts + + + + + +no-circular + + + +src/core/contracts/module-store.ts + + +module-store.ts + + + + + +src/core/contracts/index.ts->src/core/contracts/module-store.ts + + + + + +no-circular + + + +src/core/contracts/init.ts->src/types/utility.ts + + + + + +no-circular + + + +src/core/contracts/module-manager.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/core/contracts/module-manager.ts->src/core/structures/index.ts + + + + + +no-circular + + + +src/core/contracts/module-store.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/types/core-modules.ts->src/types/core-plugin.ts + + + + + +no-circular + + + +src/types/core-modules.ts->src/types/utility.ts + + + + + +no-circular + + + +src/types/core-modules.ts->src/core/index.ts + + + + + +no-circular + + + +src/core/structures/index.ts->src/core/structures/enums.ts + + + + + +src/core/structures/index.ts->src/core/structures/module-store.ts + + + + + +no-circular + + + +src/core/structures/index.ts->src/core/structures/services/index.ts + + + + + +no-circular + + + +src/core/structures/context.ts + + +context.ts + + + + + +src/core/structures/index.ts->src/core/structures/context.ts + + + + + +no-circular + + + +src/core/structures/sern-emitter.ts + + +sern-emitter.ts + + + + + +src/core/structures/index.ts->src/core/structures/sern-emitter.ts + + + + + +no-circular + + + +src/core/create-plugins.ts + + +create-plugins.ts + + + + + +src/core/create-plugins.ts->src/types/core-plugin.ts + + + + + +no-circular + + + +src/core/create-plugins.ts->src/core/structures/index.ts + + + + + +no-circular + + + +src/core/index.ts->src/core/contracts/index.ts + + + + + +no-circular + + + +src/core/index.ts->src/core/structures/index.ts + + + + + +no-circular + + + +src/core/index.ts->src/core/create-plugins.ts + + + + + +no-circular + + + +src/core/ioc/index.ts + + +index.ts + + + + + +src/core/index.ts->src/core/ioc/index.ts + + + + + +no-circular + + + +src/core/ioc/base.ts + + +base.ts + + + + + +src/core/ioc/index.ts->src/core/ioc/base.ts + + + + + +no-circular + + + +src/core/ioc/dependency-injection.ts + + +dependency-injection.ts + + + + + +src/core/ioc/index.ts->src/core/ioc/dependency-injection.ts + + + + + +no-circular + + + +src/types/ioc.ts + + +ioc.ts + + + + + +src/core/ioc/base.ts->src/types/ioc.ts + + + + + +no-circular + + + +src/core/ioc/container.ts + + +container.ts + + + + + +src/core/ioc/base.ts->src/core/ioc/container.ts + + + + + +no-circular + + + +src/core/ioc/base.ts->src/core/ioc/dependency-injection.ts + + + + + +no-circular + + + +src/types/ioc.ts->src/core/contracts/index.ts + + + + + +no-circular + + + +src/core/ioc/container.ts->src/core/_internal.ts + + + + + +no-circular + + + +src/core/ioc/container.ts->src/core/index.ts + + + + + +no-circular + + + +src/core/ioc/dependency-injection.ts->src/core/_internal.ts + + + + + +no-circular + + + +src/core/ioc/dependency-injection.ts->src/core/ioc/base.ts + + + + + +no-circular + + + +src/core/ioc/dependency-injection.ts->src/types/ioc.ts + + + + + +no-circular + + + +src/core/ioc/dependency-injection.ts->src/core/ioc/container.ts + + + + + +no-circular + + + +src/core/modules.ts + + +modules.ts + + + + + +src/core/modules.ts->src/core/_internal.ts + + + + + +src/core/modules.ts->src/types/core-plugin.ts + + + + + +src/core/modules.ts->src/types/utility.ts + + + + + +src/core/modules.ts->src/types/core-modules.ts + + + + + +src/core/modules.ts->src/core/structures/index.ts + + + + + +src/core/structures/core-context.ts + + +core-context.ts + + + + + +src/core/structures/context.ts->src/core/structures/core-context.ts + + + + + +no-circular + + + +src/core/structures/core-context.ts->src/core/_internal.ts + + + + + +no-circular + + + +src/core/structures/sern-emitter.ts->src/types/utility.ts + + + + + +no-circular + + + +src/core/structures/sern-emitter.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/core/structures/sern-emitter.ts->src/core/structures/index.ts + + + + + +no-circular + + + +src/core/structures/services/error-handling.ts->src/core/contracts/index.ts + + + + + +src/core/structures/services/logger.ts->src/core/contracts/index.ts + + + + + +src/core/structures/services/module-manager.ts->src/core/_internal.ts + + + + + +no-circular + + + +src/core/structures/services/module-manager.ts->src/core/id.ts + + + + + +no-circular + + + +src/core/structures/services/module-manager.ts->src/core/structures/enums.ts + + + + + +src/core/structures/services/module-manager.ts->src/core/contracts/index.ts + + + + + +src/core/structures/services/module-manager.ts->src/types/core-modules.ts + + + + + +no-circular + + + +src/handlers/_internal.ts + + +_internal.ts + + + + + +src/handlers/dispatchers.ts + + +dispatchers.ts + + + + + +src/handlers/_internal.ts->src/handlers/dispatchers.ts + + + + + +src/handlers/event-utils.ts + + +event-utils.ts + + + + + +src/handlers/_internal.ts->src/handlers/event-utils.ts + + + + + +src/handlers/dispatchers.ts->src/core/_internal.ts + + + + + +src/handlers/dispatchers.ts->src/types/core-plugin.ts + + + + + +src/handlers/dispatchers.ts->src/types/utility.ts + + + + + +src/handlers/dispatchers.ts->src/types/core-modules.ts + + + + + +src/handlers/dispatchers.ts->src/core/index.ts + + + + + +src/handlers/dispatchers.ts->src/handlers/event-utils.ts + + + + + +no-circular + + + +src/handlers/event-utils.ts->src/core/_internal.ts + + + + + +src/handlers/event-utils.ts->src/types/core-plugin.ts + + + + + +src/handlers/event-utils.ts->src/types/utility.ts + + + + + +src/handlers/event-utils.ts->src/types/core-modules.ts + + + + + +src/handlers/event-utils.ts->src/core/index.ts + + + + + +src/handlers/event-utils.ts->src/types/core.ts + + + + + +src/handlers/event-utils.ts->src/handlers/dispatchers.ts + + + + + +no-circular + + + +src/handlers/interaction-event.ts + + +interaction-event.ts + + + + + +src/handlers/interaction-event.ts->src/core/_internal.ts + + + + + +src/handlers/interaction-event.ts->src/core/index.ts + + + + + +src/handlers/interaction-event.ts->src/handlers/_internal.ts + + + + + +src/handlers/message-event.ts + + +message-event.ts + + + + + +src/handlers/message-event.ts->src/core/_internal.ts + + + + + +src/handlers/message-event.ts->src/core/index.ts + + + + + +src/handlers/message-event.ts->src/handlers/_internal.ts + + + + + +src/handlers/ready-event.ts + + +ready-event.ts + + + + + +src/handlers/ready-event.ts->src/core/_internal.ts + + + + + +src/handlers/ready-event.ts->src/core/contracts/index.ts + + + + + +src/handlers/ready-event.ts->src/core/structures/index.ts + + + + + +src/handlers/ready-event.ts->src/handlers/_internal.ts + + + + + +src/handlers/user-defined-events.ts + + +user-defined-events.ts + + + + + +src/handlers/user-defined-events.ts->src/core/_internal.ts + + + + + +src/handlers/user-defined-events.ts->src/core/structures/index.ts + + + + + +src/handlers/user-defined-events.ts->src/core/ioc/index.ts + + + + + +src/handlers/user-defined-events.ts->src/handlers/_internal.ts + + + + + +src/index.ts + + +index.ts + + + + + +src/index.ts->src/types/core-plugin.ts + + + + + +src/index.ts->src/types/utility.ts + + + + + +src/index.ts->src/types/core-modules.ts + + + + + +src/index.ts->src/core/index.ts + + + + + +src/index.ts->src/types/ioc.ts + + + + + +src/index.ts->src/types/core.ts + + + + + +src/index.ts->src/core/modules.ts + + + + + +src/sern.ts + + +sern.ts + + + + + +src/index.ts->src/sern.ts + + + + + +src/sern.ts->src/core/_internal.ts + + + + + +src/sern.ts->src/core/ioc/index.ts + + + + + +src/sern.ts->src/types/core.ts + + + + + +src/sern.ts->src/handlers/_internal.ts + + + + + +src/sern.ts->src/handlers/interaction-event.ts + + + + + +src/sern.ts->src/handlers/message-event.ts + + + + + +src/sern.ts->src/handlers/ready-event.ts + + + + + +src/sern.ts->src/handlers/user-defined-events.ts + + + + + +src/types/dependencies.d.ts + + +dependencies.d.ts + + + + + +src/types/dependencies.d.ts->src/types/ioc.ts + + + + + diff --git a/package.json b/package.json index 204c53c..420041a 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { "name": "@sern/handler", - "packageManager": "yarn@3.5.1", - "version": "2.6.3", + "packageManager": "yarn@3.5.0", + "version": "3.0.0", "description": "A complete, customizable, typesafe, & reactive framework for discord bots.", - "main": "dist/cjs/index.cjs", - "module": "dist/esm/index.mjs", - "types": "dist/index.d.ts", + "main": "./dist/index.js", + "module": "./dist/cjs/index.cjs", + "types": "./dist/index.d.ts", "exports": { ".": { - "import": "./dist/esm/index.mjs", - "require": "./dist/cjs/index.cjs" + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/index.d.ts" } }, "scripts": { @@ -17,10 +18,13 @@ "clean-modules": "rimraf node_modules/ && npm install", "lint": "eslint src/**/*.ts", "format": "eslint src/**/*.ts --fix", - "build:dev": "tsup && tsup --dts-only --outDir dist", - "build:prod": "tsup --minify && tsup --dts-only --outDir dist", - "publish": "npm run build:prod", - "pretty": "prettier --write ." + "build:dev": "tsup --metafile", + "build:prod": "tsup --minify", + "prepare": "npm run build:prod", + "pretty": "prettier --write .", + "tdd": "vitest", + "test": "vitest --run", + "analyze-imports": "npx depcruise src --include-only \"^src\" --output-type dot | dot -T svg > dependency-graph.svg" }, "keywords": [ "sern-handler", @@ -39,16 +43,54 @@ "ts-results-es": "^3.6.0" }, "devDependencies": { + "@faker-js/faker": "^8.0.1", "@types/node": "^18.15.11", "@typescript-eslint/eslint-plugin": "5.58.0", "@typescript-eslint/parser": "5.59.1", - "discord.js": "^14.9.0", + "dependency-cruiser": "^13.0.5", + "discord.js": "14.11.0", "esbuild": "^0.17.0", - "esbuild-ifdef": "^0.2.0", "eslint": "8.39.0", "prettier": "2.8.8", "tsup": "^6.7.0", - "typescript": "5.0.2" + "typescript": "5.0.2", + "vitest": "latest" + }, + "prettier": { + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 4, + "arrowParens": "avoid" + }, + "eslintConfig": { + "parser": "@typescript-eslint/parser", + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "script" + }, + "rules": { + "@typescript-eslint/no-non-null-assertion": "off", + "quotes": [ + 2, + "single", + { + "avoidEscape": true, + "allowTemplateLiterals": true + } + ], + "semi": [ + "error", + "always" + ], + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/ban-types": 0, + "@typescript-eslint/no-explicit-any": "off" + } }, "repository": { "type": "git", diff --git a/src/core/_internal.ts b/src/core/_internal.ts new file mode 100644 index 0000000..2899afc --- /dev/null +++ b/src/core/_internal.ts @@ -0,0 +1,9 @@ +export * as Id from './id'; +export * from './operators'; +export * from './predicates'; +export * as Files from './module-loading'; +export * from './functions'; +export type { VoidResult } from '../types/core-plugin'; +export { SernError } from './structures/enums'; +export { ModuleStore } from './structures/module-store'; +export * as DefaultServices from './structures/services'; diff --git a/src/core/contracts/emitter.ts b/src/core/contracts/emitter.ts new file mode 100644 index 0000000..abb3a29 --- /dev/null +++ b/src/core/contracts/emitter.ts @@ -0,0 +1,7 @@ +import type { AnyFunction } from '../../types/utility'; + +export interface Emitter { + addListener(eventName: string | symbol, listener: AnyFunction): this; + removeListener(eventName: string | symbol, listener: AnyFunction): this; + emit(eventName: string | symbol, ...payload: any[]): boolean; +} diff --git a/src/core/contracts/error-handling.ts b/src/core/contracts/error-handling.ts new file mode 100644 index 0000000..79d7fe7 --- /dev/null +++ b/src/core/contracts/error-handling.ts @@ -0,0 +1,21 @@ +/** + * @since 2.0.0 + */ +export interface ErrorHandling { + /** + * Number of times the process should throw an error until crashing and exiting + */ + keepAlive: number; + + /** + * @deprecated + * Version 4 will remove this method + */ + crash(err: Error): never; + /** + * A function that is called on every crash. Updates keepAlive. + * If keepAlive is 0, the process crashes. + * @param error + */ + updateAlive(error: Error): void; +} diff --git a/src/core/contracts/index.ts b/src/core/contracts/index.ts new file mode 100644 index 0000000..f19756e --- /dev/null +++ b/src/core/contracts/index.ts @@ -0,0 +1,6 @@ +export * from './error-handling'; +export * from './logging'; +export * from './module-manager'; +export * from './module-store'; +export * from './init'; +export * from './emitter'; diff --git a/src/core/contracts/init.ts b/src/core/contracts/init.ts new file mode 100644 index 0000000..eec1e4c --- /dev/null +++ b/src/core/contracts/init.ts @@ -0,0 +1,9 @@ +import type { Awaitable } from '../../types/utility'; + +/** + * Represents an initialization contract. + * Let dependencies implement this to initiate some logic. + */ +export interface Init { + init(): Awaitable; +} diff --git a/src/core/contracts/logging.ts b/src/core/contracts/logging.ts new file mode 100644 index 0000000..9ceb761 --- /dev/null +++ b/src/core/contracts/logging.ts @@ -0,0 +1,11 @@ +/** + * @since 2.0.0 + */ +export interface Logging { + error(payload: LogPayload): void; + warning(payload: LogPayload): void; + info(payload: LogPayload): void; + debug(payload: LogPayload): void; +} + +export type LogPayload = { message: T }; diff --git a/src/core/contracts/module-manager.ts b/src/core/contracts/module-manager.ts new file mode 100644 index 0000000..711582e --- /dev/null +++ b/src/core/contracts/module-manager.ts @@ -0,0 +1,22 @@ +import type { + CommandMeta, + CommandModule, + CommandModuleDefs, + Module, +} from '../../types/core-modules'; +import { CommandType } from '../structures'; + +/** + * @since 2.0.0 + */ +export interface ModuleManager { + get(id: string): string | undefined; + getMetadata(m: Module): CommandMeta | undefined; + setMetadata(m: Module, c: CommandMeta): void; + set(id: string, path: string): void; + getPublishableCommands(): Promise; + getByNameCommandType( + name: string, + commandType: T, + ): Promise | undefined; +} diff --git a/src/core/contracts/module-store.ts b/src/core/contracts/module-store.ts new file mode 100644 index 0000000..b6157b6 --- /dev/null +++ b/src/core/contracts/module-store.ts @@ -0,0 +1,9 @@ +import type { CommandMeta, Module } from '../../types/core-modules'; + +/** + * Represents a core module store that stores IDs mapped to file paths. + */ +export interface CoreModuleStore { + commands: Map; + metadata: WeakMap; +} diff --git a/src/handler/plugins/createPlugin.ts b/src/core/create-plugins.ts similarity index 84% rename from src/handler/plugins/createPlugin.ts rename to src/core/create-plugins.ts index fef8cda..fd0399a 100644 --- a/src/handler/plugins/createPlugin.ts +++ b/src/core/create-plugins.ts @@ -1,8 +1,7 @@ -import { CommandType, EventType, PluginType } from '../structures'; -import type { Plugin, PluginResult } from '../../types/plugin'; -import type { CommandArgs, EventArgs } from './args'; +import { CommandType, EventType, PluginType } from './structures'; +import type { Plugin, PluginResult, EventArgs, CommandArgs } from '../types/core-plugin'; import type { ClientEvents } from 'discord.js'; -export const guayin = Symbol('twice<3'); + export function makePlugin( type: PluginType, execute: (...args: any[]) => any, @@ -10,12 +9,11 @@ export function makePlugin( return { type, execute, - [guayin]: undefined, } as Plugin; } /** * @since 2.5.0 - * + * @__PURE__ */ export function EventInitPlugin( execute: (...args: EventArgs) => PluginResult, @@ -24,7 +22,7 @@ export function EventInitPlugin( } /** * @since 2.5.0 - * + * @__PURE__ */ export function CommandInitPlugin( execute: (...args: CommandArgs) => PluginResult, @@ -33,7 +31,7 @@ export function CommandInitPlugin( } /** * @since 2.5.0 - * + * @__PURE__ */ export function CommandControlPlugin( execute: (...args: CommandArgs) => PluginResult, @@ -42,7 +40,7 @@ export function CommandControlPlugin( } /** * @since 2.5.0 - * + * @__PURE__ */ export function EventControlPlugin( execute: (...args: EventArgs) => PluginResult, diff --git a/src/core/functions.ts b/src/core/functions.ts new file mode 100644 index 0000000..d9b8861 --- /dev/null +++ b/src/core/functions.ts @@ -0,0 +1,83 @@ +import { Err, Ok } from 'ts-results-es'; +import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js'; +import type { SernAutocompleteData, SernOptionsData } from '../types/core-modules'; +import type { AnyCommandPlugin, AnyEventPlugin, Plugin } from '../types/core-plugin'; +import { PluginType } from './structures'; +import assert from 'assert'; + +//function wrappers for empty ok / err +export const ok = /* @__PURE__*/ () => Ok.EMPTY; +export const err = /* @__PURE__*/ () => Err.EMPTY; + +export function partitionPlugins( + arr: (AnyEventPlugin | AnyCommandPlugin)[] = [], +): [Plugin[], Plugin[]] { + const controlPlugins = []; + const initPlugins = []; + + for (const el of arr) { + switch (el.type) { + case PluginType.Control: + controlPlugins.push(el); + break; + case PluginType.Init: + initPlugins.push(el); + break; + } + } + return [controlPlugins, initPlugins]; +} + +/** + * Uses an iterative DFS to check if an autocomplete node exists on the option tree + * @param iAutocomplete + * @param options + */ +export function treeSearch( + iAutocomplete: AutocompleteInteraction, + options: SernOptionsData[] | undefined, +): SernAutocompleteData | undefined { + if (options === undefined) return undefined; + //clone to prevent mutation of original command module + const _options = options.map(a => ({ ...a })); + let subcommands = new Set(); + while (_options.length > 0) { + const cur = _options.pop()!; + switch (cur.type) { + case ApplicationCommandOptionType.Subcommand: + { + subcommands.add(cur.name); + for (const option of cur.options ?? []) _options.push(option); + } + break; + case ApplicationCommandOptionType.SubcommandGroup: + { + for (const command of cur.options ?? []) _options.push(command); + } + break; + default: + { + if ('autocomplete' in cur && cur.autocomplete) { + const choice = iAutocomplete.options.getFocused(true); + assert( + 'command' in cur, + 'No command property found for autocomplete option', + ); + if (subcommands.size > 0) { + const parent = iAutocomplete.options.getSubcommand(); + const parentAndOptionMatches = + subcommands.has(parent) && cur.name === choice.name; + if (parentAndOptionMatches) { + return cur; + } + } else { + if (cur.name === choice.name) { + return cur; + } + } + } + } + break; + } + } +} diff --git a/src/core/id.ts b/src/core/id.ts new file mode 100644 index 0000000..b8975e7 --- /dev/null +++ b/src/core/id.ts @@ -0,0 +1,63 @@ +import { ApplicationCommandType, ComponentType, Interaction, InteractionType } from 'discord.js'; +import { CommandType, EventType } from './structures'; + +/** + * Construct unique ID for a given interaction object. + * @param event The interaction object for which to create an ID. + * @returns A unique string ID based on the type and properties of the interaction object. + */ +export function reconstruct(event: T) { + switch (event.type) { + case InteractionType.MessageComponent: { + return `${event.customId}_C${event.componentType}`; + } + case InteractionType.ApplicationCommand: + case InteractionType.ApplicationCommandAutocomplete: { + return `${event.commandName}_A${event.commandType}`; + } + //Modal interactions are classified as components for sern + case InteractionType.ModalSubmit: { + return `${event.customId}_C1`; + } + } +} +/** + * + * A magic number to represent any commandtype that is an ApplicationCommand. + */ +const appBitField = 0b000000001111; + +// Each index represents the exponent of a CommandType. +// Every CommandType is a power of two. +export const CommandTypeDiscordApi = [ + 1, // CommandType.Text + ApplicationCommandType.ChatInput, + ApplicationCommandType.User, + ApplicationCommandType.Message, + ComponentType.Button, + ComponentType.StringSelect, + 1, // CommandType.Modal + ComponentType.UserSelect, + ComponentType.RoleSelect, + ComponentType.MentionableSelect, + ComponentType.ChannelSelect, +]; +/* + * Generates a number based on CommandType. + * This corresponds to an ApplicationCommandType or ComponentType + * TextCommands are 0 as they aren't either or. + */ +function apiType(t: CommandType | EventType) { + if (t === CommandType.Both || t === CommandType.Modal) return 1; + return CommandTypeDiscordApi[Math.log2(t)]; +} + +/* + * Generates an id based on name and CommandType. + * A is for any ApplicationCommand. C is for any ComponentCommand + * Then, another number generated by apiType function is appended + */ +export function create(name: string, type: CommandType | EventType) { + const am = (appBitField & type) !== 0 ? 'A' : 'C'; + return name + '_' + am + apiType(type); +} diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..ef0881d --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,4 @@ +export * from './contracts'; +export * from './create-plugins'; +export * from './structures'; +export * from './ioc'; diff --git a/src/core/ioc/base.ts b/src/core/ioc/base.ts new file mode 100644 index 0000000..efb2564 --- /dev/null +++ b/src/core/ioc/base.ts @@ -0,0 +1,34 @@ +import * as assert from 'assert'; +import { composeRoot, useContainer } from './dependency-injection'; +import type { DependencyConfiguration } from '../../types/ioc'; +import { CoreContainer } from './container'; + +//SIDE EFFECT: GLOBAL DI +let containerSubject: CoreContainer>; + +/** + * Returns the underlying data structure holding all dependencies. + * Exposes methods from iti + */ +export function useContainerRaw() { + assert.ok( + containerSubject && containerSubject.isReady(), + "Could not find container or container wasn't ready. Did you call makeDependencies?", + ); + return containerSubject; +} + +/** + * @since 2.0.0 + * @param conf a configuration for creating your project dependencies + */ +export async function makeDependencies( + conf: DependencyConfiguration, +) { + //Until there are more optional dependencies, just check if the logger exists + //SIDE EFFECT + containerSubject = new CoreContainer(); + await composeRoot(containerSubject, conf); + + return useContainer(); +} diff --git a/src/core/ioc/container.ts b/src/core/ioc/container.ts new file mode 100644 index 0000000..98bbd61 --- /dev/null +++ b/src/core/ioc/container.ts @@ -0,0 +1,67 @@ +import { Container } from 'iti'; +import { SernEmitter } from '../'; +import { isAsyncFunction } from 'node:util/types'; + +import * as assert from 'node:assert'; +import { Subject } from 'rxjs'; +import { DefaultServices, ModuleStore } from '../_internal'; + +/** + * Provides all the defaults for sern to function properly. + * The only user provided dependency needs to be @sern/client + */ +export class CoreContainer> extends Container { + private ready$ = new Subject(); + private beenCalled = new Set(); + constructor() { + super(); + + this.listenForInsertions(); + + (this as Container<{}, {}>) + .add({ + '@sern/errors': () => new DefaultServices.DefaultErrorHandling(), + '@sern/emitter': () => new SernEmitter(), + '@sern/store': () => new ModuleStore(), + }) + .add(ctx => { + return { + '@sern/modules': () => + new DefaultServices.DefaultModuleManager(ctx['@sern/store']), + }; + }); + } + + private listenForInsertions() { + assert.ok( + !this.isReady(), + 'listening for init functions should only occur prior to sern being ready.', + ); + const unsubscriber = this.on('containerUpserted', e => this.callInitHooks(e)); + + this.ready$.subscribe({ + complete: unsubscriber, + }); + } + + private async callInitHooks(e: { key: keyof T; newContainer: T[keyof T] | null }) { + const dep = e.newContainer; + + assert.ok(dep); + //Ignore any dependencies that are not objects or array + if (typeof dep !== 'object' || Array.isArray(dep)) { + return; + } + if ('init' in dep && typeof dep.init === 'function' && !this.beenCalled.has(e.key)) { + isAsyncFunction(dep.init) ? await dep.init() : dep.init(); + this.beenCalled.add(e.key); + } + } + + isReady() { + return this.ready$.closed; + } + ready() { + this.ready$.unsubscribe(); + } +} diff --git a/src/core/ioc/dependency-injection.ts b/src/core/ioc/dependency-injection.ts new file mode 100644 index 0000000..39eb497 --- /dev/null +++ b/src/core/ioc/dependency-injection.ts @@ -0,0 +1,90 @@ +import type { CoreDependencies, DependencyConfiguration, IntoDependencies } from '../../types/ioc'; +import { SernError, DefaultServices } from '../_internal'; +import { useContainerRaw } from './base'; +import { CoreContainer } from './container'; + +/** + * @__PURE__ + * @since 2.0.0. + * Creates a singleton object. + * @param cb + */ +export function single(cb: () => T) { + return cb; +} + +/** + * @__PURE__ + * @since 2.0.0 + * Creates a transient object + * @param cb + */ +export function transient(cb: () => () => T) { + return cb; +} +/** + * The new Service api, a cleaner alternative to useContainer + * To obtain intellisense, ensure a .d.ts file exists in the root of compilation. + * Usually our scaffolding tool takes care of this. + * @since 3.0.0 + * @example + * ```ts + * const client = Service('@sern/client'); + * ``` + * @param key a key that corresponds to a dependency registered. + * + */ +export function Service(key: T) { + return useContainerRaw().get(key)!; +} +/** + * @since 3.0.0 + * The plural version of {@link Service} + * @returns array of dependencies, in the same order of keys provided + */ +export function Services(...keys: [...T]) { + const container = useContainerRaw(); + return keys.map(k => container.get(k)!) as IntoDependencies; +} + +/** + * Given the user's conf, check for any excluded dependency keys. + * Then, call conf.build to get the rest of the users' dependencies. + * Finally, update the containerSubject with the new container state + * @param conf + */ +export async function composeRoot( + container: CoreContainer>, + conf: DependencyConfiguration, +) { + //container should have no client or logger yet. + const hasLogger = conf.exclude?.has('@sern/logger'); + if (!hasLogger) { + container.upsert({ + '@sern/logger': () => new DefaultServices.DefaultLogging(), + }); + } + //Build the container based on the callback provided by the user + conf.build(container as CoreContainer>); + try { + container.get('@sern/client'); + } catch { + throw new Error(SernError.MissingRequired + ' No client was provided'); + } + + if (!hasLogger) { + container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); + } + + container.ready(); +} + +export function useContainer() { + console.warn(` + Warning: using a container hook (useContainer) is not recommended. + Could lead to many unwanted side effects. + Use the new Service(s) api function instead. + `); + return (...keys: [...V]) => + keys.map(key => useContainerRaw().get(key as keyof Dependencies)) as IntoDependencies; +} diff --git a/src/core/ioc/index.ts b/src/core/ioc/index.ts new file mode 100644 index 0000000..436b54e --- /dev/null +++ b/src/core/ioc/index.ts @@ -0,0 +1,2 @@ +export { useContainerRaw, makeDependencies } from './base'; +export { Service, Services, single, transient } from './dependency-injection'; diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts new file mode 100644 index 0000000..a84ff69 --- /dev/null +++ b/src/core/module-loading.ts @@ -0,0 +1,141 @@ +import { Result } from 'ts-results-es'; +import { type Observable, from, mergeMap, ObservableInput } from 'rxjs'; +import { readdir, stat } from 'fs/promises'; +import { basename, extname, join, resolve } from 'path'; +import assert from 'assert'; +import { createRequire } from 'node:module'; +import type { ImportPayload, Wrapper } from '../types/core'; +import type { Module } from '../types/core-modules'; + +export type ModuleResult = Promise>; + +/** + * Import any module based on the absolute path. + * This can accept four types of exported modules + * commonjs, javascript : + * ```js + * exports = commandModule({ }) + * + * //or + * exports.default = commandModule({ }) + * ``` + * esm javascript, typescript, and commonjs typescript + * export default commandModule({}) + */ +export async function importModule(absPath: string) { + let module = await import(absPath).then(esm => esm.default); + + assert( + module, + 'Found no default export for command module at ' + + absPath + + 'Forgot to ignore with "!"? (!filename.ts)?', + ); + if ('default' in module) { + module = module.default; + } + return Result.wrap(() => module.getInstance()).unwrapOr(module) as T; +} +export async function defaultModuleLoader(absPath: string): ModuleResult { + let module = await importModule(absPath); + assert.ok( + module, + "Found an undefined module. Forgot to ignore it with a '!' ie (!filename.ts)?", + ); + return { module, absPath }; +} + +export const fmtFileName = (n: string) => n.substring(0, n.length - 3); + +/** + * a directory string is converted into a stream of modules. + * starts the stream of modules that sern needs to process on init + * @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files + * @param commandDir + */ +export function buildModuleStream( + input: ObservableInput, +): Observable> { + return from(input).pipe(mergeMap(defaultModuleLoader)); +} + +export const getFullPathTree = (dir: string, mode: boolean) => readPaths(resolve(dir), mode); + +export const filename = (path: string) => fmtFileName(basename(path)); + +const isSkippable = (filename: string) => { + //empty string is for non extension files (directories) + const validExtensions = ['.js', '.cjs', '.mts', '.mjs', '.cts', '.ts', '']; + return filename[0] === '!' || !validExtensions.includes(extname(filename)); +}; +async function deriveFileInfo(dir: string, file: string) { + const fullPath = join(dir, file); + return { + fullPath, + fileStats: await stat(fullPath), + base: basename(file), + }; +} +async function* readPaths(dir: string, shouldDebug: boolean): AsyncGenerator { + try { + const files = await readdir(dir); + for (const file of files) { + const { fullPath, fileStats, base } = await deriveFileInfo(dir, file); + if (fileStats.isDirectory()) { + //Todo: refactor so that i dont repeat myself for files (line 71) + if (isSkippable(base)) { + if (shouldDebug) console.info(`ignored directory: ${fullPath}`); + } else { + yield* readPaths(fullPath, shouldDebug); + } + } else { + if (isSkippable(base)) { + if (shouldDebug) console.info(`ignored: ${fullPath}`); + } else { + yield 'file:///' + fullPath; + } + } + } + } catch (err) { + throw err; + } +} + +const requir = createRequire(import.meta.url); + +export function loadConfig(wrapper: Wrapper | 'file'): Wrapper { + if (wrapper === 'file') { + console.log('Experimental loading of sern.config.json'); + const config = requir(resolve('sern.config.json')) as { + language: string; + defaultPrefix?: string; + mode?: 'PROD' | 'DEV'; + paths: { + base: string; + commands: string; + events?: string; + }; + }; + const makePath = (dir: keyof typeof config.paths) => + config.language === 'typescript' + ? join('dist', config.paths[dir]!) + : join(config.paths[dir]!); + + console.log('Loading config: ', config); + const commandsPath = makePath('commands'); + + console.log('Commands path is set to', commandsPath); + let eventsPath: string | undefined; + if (config.paths.events) { + eventsPath = makePath('events'); + console.log('Events path is set to', eventsPath); + } + return { + defaultPrefix: config.defaultPrefix, + commands: commandsPath, + events: eventsPath, + mode: config.mode, + }; + } + return wrapper; +} diff --git a/src/core/modules.ts b/src/core/modules.ts new file mode 100644 index 0000000..d944f84 --- /dev/null +++ b/src/core/modules.ts @@ -0,0 +1,112 @@ +import { ClientEvents } from 'discord.js'; +import { CommandType, EventType, PluginType } from '../core/structures'; +import type { + AnyCommandPlugin, + AnyEventPlugin, + CommandArgs, + ControlPlugin, + EventArgs, + InitPlugin, +} from '../types/core-plugin'; +import type { + CommandModule, + EventModule, + InputCommand, + InputEvent, + Module, +} from '../types/core-modules'; +import { partitionPlugins } from './_internal'; +import type { Awaitable } from '../types/utility'; + +/** + * @since 1.0.0 The wrapper function to define command modules for sern + * @param mod + */ +export function commandModule(mod: InputCommand): CommandModule { + const [onEvent, plugins] = partitionPlugins(mod.plugins); + return { + ...mod, + onEvent, + plugins, + } as CommandModule; +} +/** + * @since 1.0.0 + * The wrapper function to define event modules for sern + * @param mod + */ +export function eventModule(mod: InputEvent): EventModule { + const [onEvent, plugins] = partitionPlugins(mod.plugins); + return { + ...mod, + plugins, + onEvent, + } as EventModule; +} + +/** Create event modules from discord.js client events, + * This is an {@link eventModule} for discord events, + * where typings can be very bad. + * @Experimental + * @param mod + */ +export function discordEvent(mod: { + name: T; + plugins?: AnyEventPlugin[]; + execute: (...args: ClientEvents[T]) => Awaitable; +}) { + return eventModule({ + type: EventType.Discord, + ...mod, + }); +} + +function prepareClassPlugins(c: Module) { + const [onEvent, initPlugins] = partitionPlugins(c.plugins); + c.plugins = initPlugins as InitPlugin[]; + c.onEvent = onEvent as ControlPlugin[]; +} +// +// Class modules: +// Can be refactored. +// Both implement singleton, could I make them inherit a singleton parent class? +/** + * @Experimental + * Will be refactored / changed in future + */ +export abstract class CommandExecutable { + abstract type: Type; + plugins: AnyCommandPlugin[] = []; + private static _instance: CommandModule; + + static getInstance() { + if (!CommandExecutable._instance) { + //@ts-ignore + CommandExecutable._instance = new this(); + prepareClassPlugins(CommandExecutable._instance); + } + return CommandExecutable._instance; + } + + abstract execute(...args: CommandArgs): Awaitable; +} + +/** + * @Experimental + * Will be refactored in future + */ +export abstract class EventExecutable { + abstract type: Type; + plugins: AnyEventPlugin[] = []; + + private static _instance: EventModule; + static getInstance() { + if (!EventExecutable._instance) { + //@ts-ignore + EventExecutable._instance = new this(); + prepareClassPlugins(EventExecutable._instance); + } + return EventExecutable._instance; + } + abstract execute(...args: EventArgs): Awaitable; +} diff --git a/src/core/operators.ts b/src/core/operators.ts new file mode 100644 index 0000000..8030e95 --- /dev/null +++ b/src/core/operators.ts @@ -0,0 +1,71 @@ +/** + * This file holds sern's rxjs operators used for processing data. + * Each function should be modular and testable, not bound to discord / sern + * and independent of each other. + */ +import { + concatMap, + defaultIfEmpty, + EMPTY, + every, + fromEvent, + map, + Observable, + of, + OperatorFunction, + pipe, + share, +} from 'rxjs'; +import { Emitter, ErrorHandling, Logging } from './contracts'; +import util from 'node:util'; +import type { PluginResult, VoidResult } from '../types/core-plugin'; +/** + * if {src} is true, mapTo V, else ignore + * @param item + */ +export function filterMapTo(item: () => V): OperatorFunction { + return concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY)); +} + +/** + * Calls any plugin with {args}. + * @param args if an array, its spread and plugin called. + */ +export function callPlugin(args: unknown): OperatorFunction< + { + execute: (...args: unknown[]) => PluginResult; + }, + VoidResult +> { + return concatMap(async plugin => { + if (Array.isArray(args)) { + return plugin.execute(...args); + } + return plugin.execute(args); + }); +} + +export const arrayifySource = map(src => (Array.isArray(src) ? (src as unknown[]) : [src])); + +/** + * Checks if the stream of results is all ok. + */ +export const everyPluginOk: OperatorFunction = pipe( + every(result => result.ok), + defaultIfEmpty(true), +); + +export const sharedEventStream = (e: Emitter, eventName: string) => { + return (fromEvent(e, eventName) as Observable).pipe(share()); +}; + +export function handleError(crashHandler: ErrorHandling, logging?: Logging) { + return (pload: unknown, caught: Observable) => { + // This is done to fit the ErrorHandling contract + const err = pload instanceof Error ? pload : Error(util.inspect(pload, { colors: true })); + //formatted payload + logging?.error({ message: util.inspect(pload) }); + crashHandler.updateAlive(err); + return caught; + }; +} diff --git a/src/core/predicates.ts b/src/core/predicates.ts new file mode 100644 index 0000000..a325e7a --- /dev/null +++ b/src/core/predicates.ts @@ -0,0 +1,34 @@ +import type { + AnySelectMenuInteraction, + AutocompleteInteraction, + ButtonInteraction, + ChatInputCommandInteraction, + MessageContextMenuCommandInteraction, + ModalSubmitInteraction, + UserContextMenuCommandInteraction, +} from 'discord.js'; +import { InteractionType } from 'discord.js'; + +interface InteractionTypable { + type: InteractionType; +} +//discord.js pls fix ur typings or i will >:( +type AnyMessageComponentInteraction = AnySelectMenuInteraction | ButtonInteraction; +type AnyCommandInteraction = + | ChatInputCommandInteraction + | MessageContextMenuCommandInteraction + | UserContextMenuCommandInteraction; + +export function isMessageComponent(i: InteractionTypable): i is AnyMessageComponentInteraction { + return i.type === InteractionType.MessageComponent; +} +export function isCommand(i: InteractionTypable): i is AnyCommandInteraction { + return i.type === InteractionType.ApplicationCommand; +} +export function isAutocomplete(i: InteractionTypable): i is AutocompleteInteraction { + return i.type === InteractionType.ApplicationCommandAutocomplete; +} + +export function isModal(i: InteractionTypable): i is ModalSubmitInteraction { + return i.type === InteractionType.ModalSubmit; +} diff --git a/src/handler/structures/context.ts b/src/core/structures/context.ts similarity index 54% rename from src/handler/structures/context.ts rename to src/core/structures/context.ts index 61ad0c3..5333693 100644 --- a/src/handler/structures/context.ts +++ b/src/core/structures/context.ts @@ -1,42 +1,33 @@ -import type { +import { + BaseInteraction, ChatInputCommandInteraction, Client, InteractionReplyOptions, Message, - Snowflake, MessageReplyOptions, + Snowflake, User, } from 'discord.js'; -import { Result as Either, Ok as Left, Err as Right } from 'ts-results-es'; -import type { ReplyOptions } from '../../types/handler'; -import { SernError } from './errors'; +import { CoreContext } from '../structures/core-context'; +import { Result, Ok, Err } from 'ts-results-es'; +import * as assert from 'assert'; + +type ReplyOptions = string | Omit | MessageReplyOptions; -function safeUnwrap(res: Either) { - return res.val; -} /** * @since 1.0.0 * Provides values shared between * Message and ChatInputCommandInteraction */ -export default class Context { - private constructor(private ctx: Either) {} - - /** - * Getting the Message object. Crashes if module type is - * CommandType.Slash or the event fired in a Both command was - * ChatInputCommandInteraction +export class Context extends CoreContext { + /* + * @Experimental */ - public get message() { - return this.ctx.expect(SernError.MismatchEvent); + get options() { + return this.interaction.options; } - /** - * Getting the ChatInputCommandInteraction object. Crashes if module type is - * CommandType.Text or the event fired in a Both command was - * Message - */ - public get interaction() { - return this.ctx.expectErr(SernError.MismatchEvent); + protected constructor(protected ctx: Result) { + super(ctx); } public get id(): Snowflake { @@ -65,7 +56,6 @@ export default class Context { public get guildId() { return this.ctx.val.guildId; } - /* * interactions can return APIGuildMember if the guild it is emitted from is not cached */ @@ -80,22 +70,8 @@ export default class Context { public get inGuild(): boolean { return this.ctx.val.inGuild(); } - public isMessage() { - return this.ctx.map(() => true).unwrapOr(false); - } - public isSlash() { - return !this.isMessage(); - } - - static wrap(wrappable: ChatInputCommandInteraction | Message): Context { - if ('token' in wrappable) { - return new Context(Right(wrappable)); - } - return new Context(Left(wrappable)); - } - - public reply(content: ReplyOptions) { + public async reply(content: ReplyOptions) { return safeUnwrap( this.ctx .map(m => m.reply(content as string | MessageReplyOptions)) @@ -104,4 +80,16 @@ export default class Context { ), ); } + + static override wrap(wrappable: BaseInteraction | Message): Context { + if ('interaction' in wrappable) { + return new Context(Ok(wrappable)); + } + assert.ok(wrappable.isChatInputCommand()); + return new Context(Err(wrappable)); + } +} + +function safeUnwrap(res: Result) { + return res.val; } diff --git a/src/core/structures/core-context.ts b/src/core/structures/core-context.ts new file mode 100644 index 0000000..08a0e78 --- /dev/null +++ b/src/core/structures/core-context.ts @@ -0,0 +1,32 @@ +import { Result as Either } from 'ts-results-es'; +import { SernError } from '../_internal'; +import * as assert from 'node:assert'; + +/** + * @since 3.0.0 + */ +export abstract class CoreContext { + protected constructor(protected ctx: Either) { + assert.ok(typeof ctx.val === 'object' && ctx.val != null); + } + get message(): M { + return this.ctx.expect(SernError.MismatchEvent); + } + get interaction(): I { + return this.ctx.expectErr(SernError.MismatchEvent); + } + + public isMessage(): this is CoreContext { + return this.ctx.ok; + } + + public isSlash(): this is CoreContext { + return !this.isMessage(); + } + //todo: add agnostic options resolver for Context + abstract get options(): unknown; + + static wrap(_: unknown): unknown { + throw Error('You need to override this method; cannot wrap an abstract class'); + } +} diff --git a/src/handler/structures/enums.ts b/src/core/structures/enums.ts similarity index 58% rename from src/handler/structures/enums.ts rename to src/core/structures/enums.ts index f1cfa9b..663785e 100644 --- a/src/handler/structures/enums.ts +++ b/src/core/structures/enums.ts @@ -15,45 +15,18 @@ * ``` */ export enum CommandType { - /** - * The CommandType for text commands - */ - Text = 1, - /** - * The CommandType for slash commands - */ - Slash = 2, - /** - * The CommandType for hybrid commands, text and slash - */ + Text = 1 << 0, + Slash = 1 << 1, Both = 3, - /** - * The CommandType for UserContextMenuInteraction commands - */ - CtxUser = 4, - /** - * The CommandType for MessageContextMenuInteraction commands - */ - CtxMsg = 8, - /** - * The CommandType for ButtonInteraction commands - */ - Button = 16, - /** - * The CommandType for StringSelectMenuInteraction commands - */ - StringSelect = 32, - /** - * The CommandType for ModalSubmitInteraction commands - */ - Modal = 64, - /** - * The CommandType for the other SelectMenuInteractions - */ - ChannelSelect = 256, - MentionableSelect = 512, - RoleSelect = 1024, - UserSelect = 2048, + CtxUser = 1 << 2, + CtxMsg = 1 << 3, + Button = 1 << 4, + StringSelect = 1 << 5, + Modal = 1 << 6, + UserSelect = 1 << 7, + RoleSelect = 1 << 8, + MentionableSelect = 1 << 9, + ChannelSelect = 1 << 10, } /** @@ -106,16 +79,6 @@ export enum PluginType { * The PluginType for InitPlugins */ Init = 1, - /** - * @deprecated - * Use PluginType.Init instead - */ - Command = 1, - /** - * @deprecated - * Use PluginType.Control instead - */ - Event = 2, /** * The PluginType for EventPlugins */ @@ -138,3 +101,42 @@ export enum PayloadType { */ Warning = 'warning', } + +/** + * @enum { string } + */ +export const enum SernError { + /** + * Throws when registering an invalid module. + * This means it is undefined or an invalid command type was provided + */ + InvalidModuleType = 'Detected an unknown module type', + /** + * Attempted to lookup module in command module store. Nothing was found! + */ + UndefinedModule = `A module could not be detected`, + /** + * Attempted to lookup module in command module store. Nothing was found! + */ + MismatchModule = `A module type mismatched with event emitted!`, + /** + * Unsupported interaction at this moment. + */ + NotSupportedInteraction = `This interaction is not supported.`, + /** + * One plugin called `controller.stop()` (end command execution / loading) + */ + PluginFailure = `A plugin failed to call controller.next()`, + /** + * A crash that occurs when accessing an invalid property of Context + */ + MismatchEvent = `You cannot use message when an interaction fired or vice versa`, + /** + * Unsupported feature attempted to access at this time + */ + NotSupportedYet = `This feature is not supported yet`, + /** + * Required Dependency not found + */ + MissingRequired = `@sern/client is required but was not found`, +} diff --git a/src/core/structures/index.ts b/src/core/structures/index.ts new file mode 100644 index 0000000..e3c08dc --- /dev/null +++ b/src/core/structures/index.ts @@ -0,0 +1,5 @@ +export { CommandType, PluginType, PayloadType, EventType } from './enums'; +export * from './context'; +export * from './sern-emitter'; +export * from './services'; +export * from './module-store'; diff --git a/src/core/structures/module-store.ts b/src/core/structures/module-store.ts new file mode 100644 index 0000000..6e754e1 --- /dev/null +++ b/src/core/structures/module-store.ts @@ -0,0 +1,12 @@ +import { CommandMeta, Module } from '../../types/core-modules'; +import { CoreModuleStore } from '../contracts'; + +/* + * @internal + * Version 4.0.0 will internalize this api. Please refrain from using ModuleStore! + * For interacting with modules, use the ModuleManager instead. + */ +export class ModuleStore implements CoreModuleStore { + metadata = new WeakMap(); + commands = new Map(); +} diff --git a/src/handler/sernEmitter.ts b/src/core/structures/sern-emitter.ts similarity index 88% rename from src/handler/sernEmitter.ts rename to src/core/structures/sern-emitter.ts index 82d6077..0b4f188 100644 --- a/src/handler/sernEmitter.ts +++ b/src/core/structures/sern-emitter.ts @@ -1,12 +1,15 @@ -import { EventEmitter } from 'events'; -import type { Payload, SernEventsMapping } from '../types/handler'; -import { PayloadType } from './structures'; -import type { Module } from '../types/module'; +import { EventEmitter } from 'node:events'; +import { PayloadType } from '../../core/structures'; +import { Module } from '../../types/core-modules'; +import { SernEventsMapping, Payload } from '../../types/utility'; /** * @since 1.0.0 */ -class SernEmitter extends EventEmitter { +export class SernEmitter extends EventEmitter { + constructor() { + super({ captureRejections: true }); + } /** * Listening to sern events with on. This event stays on until a crash or a normal exit * @param eventName @@ -84,5 +87,3 @@ class SernEmitter extends EventEmitter { ); } } - -export default SernEmitter; diff --git a/src/core/structures/services/error-handling.ts b/src/core/structures/services/error-handling.ts new file mode 100644 index 0000000..f5ef972 --- /dev/null +++ b/src/core/structures/services/error-handling.ts @@ -0,0 +1,21 @@ +import { ErrorHandling } from '../../contracts'; + +/** + * @internal + * @since 2.0.0 + * Version 4.0.0 will internalize this api. Please refrain from using ModuleStore! + */ +export class DefaultErrorHandling implements ErrorHandling { + crash(err: Error): never { + throw err; + } + + keepAlive = 5; + + updateAlive(err: Error) { + this.keepAlive--; + if (this.keepAlive === 0) { + throw err; + } + } +} diff --git a/src/core/structures/services/index.ts b/src/core/structures/services/index.ts new file mode 100644 index 0000000..3f1d4ab --- /dev/null +++ b/src/core/structures/services/index.ts @@ -0,0 +1,3 @@ +export * from './error-handling'; +export * from './logger'; +export * from './module-manager'; diff --git a/src/handler/contracts/logging.ts b/src/core/structures/services/logger.ts similarity index 68% rename from src/handler/contracts/logging.ts rename to src/core/structures/services/logger.ts index f431f43..5de19c7 100644 --- a/src/handler/contracts/logging.ts +++ b/src/core/structures/services/logger.ts @@ -1,15 +1,9 @@ -import type { LogPayload } from '../../types/handler'; -/** - * @since 2.0.0 - */ -export interface Logging { - error(payload: LogPayload): void; - warning(payload: LogPayload): void; - info(payload: LogPayload): void; - debug(payload: LogPayload): void; -} +import { LogPayload, Logging } from '../../contracts'; + /** + * @internal * @since 2.0.0 + * Version 4.0.0 will internalize this api. Please refrain from using ModuleStore! */ export class DefaultLogging implements Logging { private date = () => new Date(); diff --git a/src/core/structures/services/module-manager.ts b/src/core/structures/services/module-manager.ts new file mode 100644 index 0000000..74f1aa7 --- /dev/null +++ b/src/core/structures/services/module-manager.ts @@ -0,0 +1,50 @@ +import * as Id from '../../../core/id'; +import { CoreModuleStore, ModuleManager } from '../../contracts'; +import { Files } from '../../_internal'; +import { CommandMeta, CommandModule, CommandModuleDefs, Module } from '../../../types/core-modules'; +import { CommandType } from '../enums'; +/** + * @internal + * @since 2.0.0 + * Version 4.0.0 will internalize this api. Please refrain from using DefaultModuleManager! + */ +export class DefaultModuleManager implements ModuleManager { + constructor(private moduleStore: CoreModuleStore) {} + + getByNameCommandType(name: string, commandType: T) { + const id = this.get(Id.create(name, commandType)); + if (!id) { + return undefined; + } + return Files.importModule(id); + } + + setMetadata(m: Module, c: CommandMeta): void { + this.moduleStore.metadata.set(m, c); + } + + getMetadata(m: Module): CommandMeta { + const maybeModule = this.moduleStore.metadata.get(m); + if (!maybeModule) { + throw Error('Could not find metadata in store for ' + m); + } + return maybeModule; + } + + get(id: string) { + return this.moduleStore.commands.get(id); + } + set(id: string, path: string): void { + this.moduleStore.commands.set(id, path); + } + //not tested + getPublishableCommands(): Promise { + const entries = this.moduleStore.commands.entries(); + const publishable = 0b000000110; + return Promise.all( + Array.from(entries) + .filter(([id]) => !(Number.parseInt(id.at(-1)!) & publishable)) + .map(([, path]) => Files.importModule(path)), + ); + } +} diff --git a/src/handler/contracts/errorHandling.ts b/src/handler/contracts/errorHandling.ts deleted file mode 100644 index 6fc053a..0000000 --- a/src/handler/contracts/errorHandling.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Observable } from 'rxjs'; -import type { Logging } from './logging'; -import util from 'util'; -/** - * @since 2.0.0 - */ -export interface ErrorHandling { - /** - * Number of times the process should throw an error until crashing and exiting - */ - keepAlive: number; - - /** - * Utility function to crash - * @param error - */ - crash(error: Error): never; - - /** - * A function that is called on every crash. Updates keepAlive - * @param error - */ - updateAlive(error: Error): void; -} -/** - * @since 2.0.0 - */ -export class DefaultErrorHandling implements ErrorHandling { - keepAlive = 5; - crash(error: Error): never { - throw error; - } - updateAlive(_: Error) { - this.keepAlive--; - } -} - -export function handleError(crashHandler: ErrorHandling, logging?: Logging) { - return (pload: unknown, caught: Observable) => { - // This is done to fit the ErrorHandling contract - const err = pload instanceof Error ? pload : Error(util.format(pload)); - if (crashHandler.keepAlive == 0) { - crashHandler.crash(err); - } - //formatted payload - logging?.error({ message: util.format(pload) }); - crashHandler.updateAlive(err); - return caught; - }; -} diff --git a/src/handler/contracts/index.ts b/src/handler/contracts/index.ts deleted file mode 100644 index ad878b1..0000000 --- a/src/handler/contracts/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { ErrorHandling, DefaultErrorHandling } from './errorHandling'; -export { Logging, DefaultLogging } from './logging'; -export { ModuleManager, DefaultModuleManager } from './moduleManager'; diff --git a/src/handler/contracts/moduleManager.ts b/src/handler/contracts/moduleManager.ts deleted file mode 100644 index b7da765..0000000 --- a/src/handler/contracts/moduleManager.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { CommandModuleDefs } from '../../types/module'; -import type { CommandType, ModuleStore } from '../structures'; -import type { Processed } from '../../types/handler'; -/** - * @since 2.0.0 - */ -export interface ModuleManager { - get( - strat: (ms: ModuleStore) => Processed | undefined, - ): Processed | undefined; - set(strat: (ms: ModuleStore) => void): void; -} -/** - * @since 2.0.0 - */ -export class DefaultModuleManager implements ModuleManager { - constructor(private moduleStore: ModuleStore) {} - get( - strat: (ms: ModuleStore) => Processed | undefined, - ) { - return strat(this.moduleStore); - } - - set(strat: (ms: ModuleStore) => void): void { - strat(this.moduleStore); - } -} diff --git a/src/handler/dependencies/index.ts b/src/handler/dependencies/index.ts deleted file mode 100644 index dde2b2b..0000000 --- a/src/handler/dependencies/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { single, transient, many } from './lifetimeFunctions'; -export { useContainerRaw } from './provider'; diff --git a/src/handler/dependencies/lifetimeFunctions.ts b/src/handler/dependencies/lifetimeFunctions.ts deleted file mode 100644 index 3fd66e6..0000000 --- a/src/handler/dependencies/lifetimeFunctions.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { _const } from '../utilities/functions'; - -type NotFunction = - | string - | number - | boolean - | null - | undefined - | bigint - | readonly any[] - | { apply?: never; [k: string]: any } - | { call?: never; [k: string]: any }; - -/** - * @deprecated - * @param cb - */ -export function single(cb: T): () => T; -/** - * New signature - * @since 2.0.0 - * @param cb - */ -export function single unknown>(cb: T): T; -/** - * @__PURE__ - * @since 2.0.0. - * Please note that on intellij, the deprecation is for all signatures, which is unintended behavior (and - * very annoying). - * For future versions, ensure that single is being passed as a **callback!!** - * @param cb - */ -export function single(cb: T) { - if (typeof cb === 'function') return cb; - return () => cb; -} -/** - * @deprecated - * @param cb - * Deprecated signature - */ -export function transient(cb: T): () => () => T; -export function transient () => unknown>(cb: T): T; -/** - * @__PURE__ - * @since 2.0.0 - * Following iti's singleton and transient implementation, - * use transient if you want a new dependency every time your container getter is called - * @param cb - */ -export function transient(cb: (() => () => T) | T) { - if (typeof cb !== 'function') return () => () => cb; - return cb; -} - -/** - * @__PURE__ - * @deprecated - * @param value - * Please use the transient function instead - */ -// prettier-ignore -export const many = (value: T) => () => _const(value); diff --git a/src/handler/dependencies/provider.ts b/src/handler/dependencies/provider.ts deleted file mode 100644 index 7acf3e4..0000000 --- a/src/handler/dependencies/provider.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { Container } from 'iti'; -import type { Dependencies, DependencyConfiguration, MapDeps } from '../../types/handler'; -import SernEmitter from '../sernEmitter'; -import { DefaultErrorHandling, DefaultLogging, DefaultModuleManager } from '../contracts'; -import { Result } from 'ts-results-es'; -import { BehaviorSubject } from 'rxjs'; -import { createContainer } from 'iti'; -import { type Wrapper, ModuleStore, SernError } from '../structures'; - -export const containerSubject = new BehaviorSubject(defaultContainer()); - -/** - * Given the user's conf, check for any excluded dependency keys. - * Then, call conf.build to get the rest of the users' dependencies. - * Finally, update the containerSubject with the new container state - * @param conf - */ -export function composeRoot(conf: DependencyConfiguration) { - //Get the current container. This should have no client or possible logger yet. - const currentContainer = containerSubject.getValue(); - const excludeLogger = conf.exclude?.has('@sern/logger'); - if (!excludeLogger) { - currentContainer.add({ - '@sern/logger': () => new DefaultLogging(), - }); - } - //Build the container based on the callback provided by the user - const container = conf.build(currentContainer); - //Check if the built container contains @sern/client or throw - // a runtime exception - Result.wrap(() => container.get('@sern/client')).expect(SernError.MissingRequired); - - if (!excludeLogger) { - container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' }); - } - //I'm sorry little one - containerSubject.next(container as any); -} - -export function useContainer() { - const container = containerSubject.getValue() as Container; - return (...keys: [...V]) => - keys.map(key => Result.wrap(() => container.get(key)).unwrapOr(undefined)) as MapDeps; -} - -/** - * Returns the underlying data structure holding all dependencies. - * Please be careful as this only gets the client's current state. - * Exposes some methods from iti - */ -export function useContainerRaw() { - return containerSubject.getValue() as Container; -} - -/** - * Provides all the defaults for sern to function properly. - * The only user provided dependency needs to be @sern/client - */ -function defaultContainer() { - return createContainer() - .add({ '@sern/errors': () => new DefaultErrorHandling() }) - .add({ '@sern/store': () => new ModuleStore() }) - .add(ctx => { - return { - '@sern/modules': () => new DefaultModuleManager(ctx['@sern/store']), - }; - }) - .add({ '@sern/emitter': () => new SernEmitter() }) as Container< - Omit, - {} - >; -} - -export function makeFetcher(wrapper: Wrapper) { - const requiredDependencyKeys = [ - '@sern/emitter', - '@sern/client', - '@sern/errors', - '@sern/logger', - ] as ['@sern/emitter', '@sern/client', '@sern/errors', '@sern/logger']; - return (otherKeys: [...Keys]) => - wrapper.containerConfig.get(...requiredDependencyKeys, ...otherKeys) as MapDeps< - Dependencies, - [...typeof requiredDependencyKeys, ...Keys] - >; -} diff --git a/src/handler/events/dispatchers/dispatchers.ts b/src/handler/events/dispatchers/dispatchers.ts deleted file mode 100644 index e966c4c..0000000 --- a/src/handler/events/dispatchers/dispatchers.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { Processed } from '../../../types/handler'; -import type { AutocompleteInteraction } from 'discord.js'; -import { SernError } from '../../structures'; -import treeSearch from '../../utilities/treeSearch'; -import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../types/module'; -import { EventEmitter } from 'events'; -import * as assert from 'assert'; -import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; -import { arrayifySource, callPlugin } from '../operators'; -import { createResultResolver } from '../observableHandling'; - -export function dispatchCommand(module: Processed, createArgs: () => unknown[]) { - const args = createArgs(); - return { - module, - args, - }; -} - -function intoPayload(module: Processed) { - return pipe( - arrayifySource, - map(args => ({ module, args })), - ); -} - -const createResult = createResultResolver< - Processed, - { module: Processed; args: unknown[] }, - unknown[] ->({ - createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)), - onNext: ({ args }) => args, -}); -/** - * Creates an observable from { source } - * @param module - * @param source - */ -export function eventDispatcher(module: Processed, source: unknown) { - assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); - - const execute: OperatorFunction = concatMap(async args => - module.execute(...args), - ); - return fromEvent(source, module.name).pipe( - intoPayload(module), - concatMap(createResult), - execute, - ); -} - -export function dispatchAutocomplete( - module: Processed, - interaction: AutocompleteInteraction, -) { - const option = treeSearch(interaction, module.options); - if (option !== undefined) { - return { - module: option.command as Processed, //autocomplete is not a true "module" warning cast! - args: [interaction], - }; - } - throw Error( - SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`, - ); -} diff --git a/src/handler/events/dispatchers/provideArgs.ts b/src/handler/events/dispatchers/provideArgs.ts deleted file mode 100644 index 33aa91e..0000000 --- a/src/handler/events/dispatchers/provideArgs.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { ChatInputCommandInteraction, Interaction, Message } from 'discord.js'; -import { Context } from '../../structures'; -import type { Args, SlashOptions } from '../../../types/handler'; - -/** - * function overloads to create an arguments list for Context - * @param wrap - * @param messageArgs - */ -export function contextArgs( - wrap: Message, - messageArgs?: string[], -): () => [Context, ['text', string[]]]; -export function contextArgs(wrap: Interaction): () => [Context, ['slash', SlashOptions]]; -export function contextArgs(wrap: Interaction | Message, messageArgs?: string[]) { - const ctx = Context.wrap(wrap as ChatInputCommandInteraction | Message); - const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.interaction.options]; - return () => [ctx, args] as [Context, Args]; -} - -export function interactionArg(interaction: T) { - return () => [interaction] as [T]; -} diff --git a/src/handler/events/interactionHandler.ts b/src/handler/events/interactionHandler.ts deleted file mode 100644 index 1febb38..0000000 --- a/src/handler/events/interactionHandler.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Interaction } from 'discord.js'; -import { - catchError, - concatMap, - EMPTY, - filter, - finalize, - fromEvent, - map, - Observable, - of, - OperatorFunction, - pipe, -} from 'rxjs'; -import { CommandType, type ModuleStore, SernError } from '../structures'; -import { contextArgs, dispatchAutocomplete, dispatchCommand, interactionArg } from './dispatchers'; -import { executeModule, makeModuleExecutor } from './observableHandling'; -import type { CommandModule } from '../../types/module'; -import { ErrorHandling, handleError } from '../contracts/errorHandling'; -import SernEmitter from '../sernEmitter'; -import type { Processed } from '../../types/handler'; -import { useContainerRaw } from '../dependencies'; -import type { Logging, ModuleManager } from '../contracts'; -import type { EventEmitter } from 'node:events'; - -function makeInteractionProcessor( - modules: ModuleManager, -): OperatorFunction; event: Interaction }> { - const get = (cb: (ms: ModuleStore) => Processed | undefined) => { - return modules.get(cb); - }; - return pipe( - concatMap(event => { - if (event.isMessageComponent()) { - const customId = event.customId; - const module = get(ms => { - return ms.InteractionHandlers[event.componentType].get(customId); - }); - return of({ module, event }); - } else if (event.isCommand() || event.isAutocomplete()) { - const commandName = event.commandName; - const module = get( - ms => - /** - * try to fetch from ApplicationCommands, if nothing, try BothCommands - * exists on the API but not sern - */ - ms.ApplicationCommands[event.commandType].get(commandName) ?? - ms.BothCommands.get(commandName), - ); - return of({ module, event }); - } else if (event.isModalSubmit()) { - const module = get(ms => ms.ModalSubmit.get(event.customId)); - return of({ module, event }); - } else return EMPTY; - }), - filter(m => m.module !== undefined), - ) as OperatorFunction; event: Interaction }>; -} - -export function makeInteractionCreate([s, client, err, log, modules]: [ - SernEmitter, - EventEmitter, - ErrorHandling, - Logging | undefined, - ModuleManager, -]) { - //map. If nothing again,this means a slash command - const interactionStream$ = fromEvent(client, 'interactionCreate') as Observable; - const interactionProcessor = makeInteractionProcessor(modules); - return interactionStream$ - .pipe( - interactionProcessor, - map(createDispatcher), - makeModuleExecutor(module => { - s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); - }), - concatMap(module => executeModule(s, module)), - catchError(handleError(err, log)), - finalize(() => { - log?.info({ - message: 'interactionCreate stream closed or reached end of lifetime', - }); - useContainerRaw() - ?.disposeAll() - .then(() => log?.info({ message: 'Cleaning container and crashing' })); - }), - ) - .subscribe(); -} - -function createDispatcher({ - module, - event, -}: { - event: Interaction; - module: Processed; -}) { - switch (module.type) { - case CommandType.Text: - throw Error(SernError.MismatchEvent); - case CommandType.Slash: - case CommandType.Both: { - if (event.isAutocomplete()) { - /** - * Autocomplete is a special case that - * must be handled separately, since it's - * too different from regular command modules - */ - return dispatchAutocomplete(module, event); - } else { - return dispatchCommand(module, contextArgs(event)); - } - } - default: - return dispatchCommand(module, interactionArg(event)); - } -} diff --git a/src/handler/events/messageHandler.ts b/src/handler/events/messageHandler.ts deleted file mode 100644 index b76e070..0000000 --- a/src/handler/events/messageHandler.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of, pipe } from 'rxjs'; -import { type ModuleStore, SernError } from '../structures'; -import type { Message } from 'discord.js'; -import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; -import type { CommandModule, TextCommand } from '../../types/module'; -import { ErrorHandling, handleError } from '../contracts/errorHandling'; -import { contextArgs, dispatchCommand } from './dispatchers'; -import SernEmitter from '../sernEmitter'; -import type { Processed } from '../../types/handler'; -import { useContainerRaw } from '../dependencies'; -import type { Logging, ModuleManager } from '../contracts'; -import type { EventEmitter } from 'node:events'; - -/** - * Removes the first character(s) _[depending on prefix length]_ of the message - * @param msg - * @param prefix The prefix to remove - * @returns The message without the prefix - * @example - * message.content = '!ping'; - * console.log(fmt(message, '!')); - * // [ 'ping' ] - */ -export function fmt(msg: string, prefix: string): string[] { - return msg.slice(prefix.length).trim().split(/\s+/g); -} - -/** - * An operator function that processes a message to fetch a command module and prepares context payload. - * @param defaultPrefix - * @param get - */ -const createMessageProcessor = ( - defaultPrefix: string, - get: ( - cb: (ms: ModuleStore) => Processed | undefined, - ) => CommandModule | undefined, -) => - pipe( - ignoreNonBot(defaultPrefix), - //This concatMap checks if module is undefined, and if it is, do not continue. - // Synonymous to filterMap, but I haven't thought of a generic implementation for filterMap yet - concatMap(message => { - const [prefix, ...rest] = fmt(message.content, defaultPrefix); - const module = get(ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix)); - if (module === undefined) { - return EMPTY; - } - const payload = { - args: contextArgs(message, rest), - module, - }; - return of(payload); - }), - map(({ args, module }) => dispatchCommand(module as Processed, args)), - ); - -export function makeMessageCreate( - [s, client, err, log, modules]: [ - SernEmitter, - EventEmitter, - ErrorHandling, - Logging | undefined, - ModuleManager, - ], - defaultPrefix?: string, -) { - if (!defaultPrefix) { - return EMPTY.subscribe(); - } - const get = (cb: (ms: ModuleStore) => Processed | undefined) => { - return modules.get(cb); - }; - const messageStream$ = fromEvent(client, 'messageCreate') as Observable; - const messageProcessor = createMessageProcessor(defaultPrefix, get); - return messageStream$ - .pipe( - messageProcessor, - makeModuleExecutor(module => { - s.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); - }), - concatMap(payload => executeModule(s, payload)), - catchError(handleError(err, log)), - finalize(() => { - log?.info({ message: 'messageCreate stream closed or reached end of lifetime' }); - useContainerRaw() - ?.disposeAll() - .then(() => log?.info({ message: 'Cleaning container and crashing' })); - }), - ) - .subscribe(); -} diff --git a/src/handler/events/observableHandling.ts b/src/handler/events/observableHandling.ts deleted file mode 100644 index 43bc2e3..0000000 --- a/src/handler/events/observableHandling.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { Awaitable, Message } from 'discord.js'; -import { concatMap, EMPTY, filter, from, Observable, of, tap, throwError } from 'rxjs'; -import { Result } from 'ts-results-es'; -import type { CommandModule, EventModule, Module } from '../../types/module'; -import SernEmitter from '../sernEmitter'; -import { callPlugin, everyPluginOk, filterMapTo } from './operators'; -import type { ImportPayload, Processed } from '../../types/handler'; -import type { ControlPlugin, VoidResult } from '../../types/plugin'; - -function hasPrefix(prefix: string, content: string) { - const prefixInContent = content.slice(0, prefix.length); - return prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0; -} - -/** - * Ignores messages from any person / bot except itself - * @param prefix - */ -export function ignoreNonBot(prefix: string) { - const messageFromHumanAndHasPrefix = ({ author, content }: Message) => - !author.bot && hasPrefix(prefix, content); - return filter(messageFromHumanAndHasPrefix); -} - -/** - * Wraps the task in a Result as a try / catch. - * if the task is ok, an event is emitted and the stream becomes empty - * if the task is an error, throw an error down the stream which will be handled by catchError - * @param emitter reference to SernEmitter that will emit a successful execution of module - * @param module the module that will be executed with task - * @param task the deferred execution which will be called - */ -export function executeModule( - emitter: SernEmitter, - { - module, - task, - }: { - module: Processed; - task: () => Awaitable; - }, -) { - return of(module).pipe( - //converting the task into a promise so rxjs can resolve the Awaitable properly - concatMap(() => Result.wrapAsync(async () => task())), - concatMap(result => { - if (result.ok) { - emitter.emit('module.activate', SernEmitter.success(module)); - return EMPTY; - } else { - return throwError(() => SernEmitter.failure(module, result.val)); - } - }), - ); -} - -/** - * A higher order function that - * - creates a stream of {@link VoidResult} { config.createStream } - * - any failures results to { config.onFailure } being called - * - if all results are ok, the stream is converted to { config.onSuccess } - * emit config.onSuccess Observable - * @param config - * @returns receiver function for flattening a stream of data - */ -export function createResultResolver< - T extends { execute: (...args: any[]) => any; onEvent: ControlPlugin[] }, - Args extends { module: T; [key: string]: unknown }, - Output, ->(config: { - onStop?: (module: T) => unknown; - onNext: (args: Args) => Output; - createStream: (args: Args) => Observable; -}) { - return (args: Args) => { - const task$ = config.createStream(args); - return task$.pipe( - tap(result => { - if (result.err) { - config.onStop?.(args.module); - } - }), - everyPluginOk, - filterMapTo(() => config.onNext(args)), - ); - }; -} - -/** - * Calls a module's init plugins and checks for Err. If so, call { onStop } and - * ignore the module - */ -export function callInitPlugins< - T extends Processed, - Args extends ImportPayload, ->(config: { onStop?: (module: T) => unknown; onNext: (module: Args) => T }) { - return concatMap( - createResultResolver({ - createStream: args => from(args.module.plugins).pipe(callPlugin(args)), - ...config, - }), - ); -} - -/** - * Creates an executable task ( execute the command ) if all control plugins are successful - * @param onStop emits a failure response to the SernEmitter - */ -export function makeModuleExecutor< - M extends Processed, - Args extends { module: M; args: unknown[] }, ->(onStop: (m: M) => unknown) { - const onNext = ({ args, module }: Args) => ({ task: () => module.execute(...args), module }); - return concatMap( - createResultResolver({ - onStop, - createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), - onNext, - }), - ); -} diff --git a/src/handler/events/operators/index.ts b/src/handler/events/operators/index.ts deleted file mode 100644 index 9c03195..0000000 --- a/src/handler/events/operators/index.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * This file holds sern's rxjs operators used for processing data. - * Each function should be modular and testable, not bound to discord / sern - * and independent of each other - */ - -import { concatMap, defaultIfEmpty, EMPTY, every, map, of, OperatorFunction, pipe } from 'rxjs'; -import type { AnyModule } from '../../../types/module'; -import { nameOrFilename } from '../../utilities/functions'; -import type { PluginResult, VoidResult } from '../../../types/plugin'; -import { guayin } from '../../plugins'; -import { controller } from '../../sern'; -import { Result } from 'ts-results-es'; -import { ImportPayload, Processed } from '../../../types/handler'; -/** - * if {src} is true, mapTo V, else ignore - * @param item - */ -export function filterMapTo(item: () => V): OperatorFunction { - return concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY)); -} - -/** - * Calls any plugin with {args}. - * @param args if an array, its spread and plugin called. - */ -export function callPlugin(args: unknown): OperatorFunction< - { - execute: (...args: unknown[]) => PluginResult; - }, - VoidResult -> { - return concatMap(async plugin => { - const isNewPlugin = Reflect.has(plugin, guayin); - if (isNewPlugin) { - if (Array.isArray(args)) { - return plugin.execute(...args); - } - return plugin.execute(args); - } else { - return plugin.execute(args, controller); - } - }); -} - -export const arrayifySource = map(src => (Array.isArray(src) ? (src as unknown[]) : [src])); - -export const fillDefaults = ({ module, absPath }: ImportPayload) => { - return { - absPath, - module: { - name: nameOrFilename(module?.name, absPath), - description: module?.description ?? '...', - ...module, - }, - }; -}; - -/** - * If the current value in Result stream is an error, calls callback. - * This also extracts the Ok value from Result - * @param cb - * @returns Observable<{ module: T; absPath: string }> - */ -export function errTap(cb: (err: Err) => void): OperatorFunction, Ok> { - return concatMap(result => { - if (result.ok) { - return of(result.val); - } else { - cb(result.val as Err); - return EMPTY; - } - }); -} - -/** - * Checks if the stream of results is all ok. - */ -export const everyPluginOk: OperatorFunction = pipe( - every(result => result.ok), - defaultIfEmpty(true), -); diff --git a/src/handler/events/readyHandler.ts b/src/handler/events/readyHandler.ts deleted file mode 100644 index 4e489ab..0000000 --- a/src/handler/events/readyHandler.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { fromEvent, map, pipe, switchMap, take } from 'rxjs'; -import * as Files from '../module-loading/readFile'; -import { callInitPlugins } from './observableHandling'; -import { CommandType, type ModuleStore, SernError } from '../structures'; -import { Result } from 'ts-results-es'; -import { ApplicationCommandType, ComponentType } from 'discord.js'; -import type { CommandModule } from '../../types/module'; -import type { Processed } from '../../types/handler'; -import type { ErrorHandling, Logging, ModuleManager } from '../contracts'; -import { err, ok } from '../utilities/functions'; -import { errTap, fillDefaults } from './operators'; -import SernEmitter from '../sernEmitter'; -import type { EventEmitter } from 'node:events'; - -function buildCommandModules(commandDir: string, sernEmitter: SernEmitter) { - return pipe( - switchMap(() => Files.buildModuleStream(commandDir)), - errTap(error => { - sernEmitter.emit('module.register', SernEmitter.failure(undefined, error)); - }), - map(fillDefaults), - ); -} -export function makeReadyEvent( - [sEmitter, client, errorHandler, , moduleManager]: [ - SernEmitter, - EventEmitter, - ErrorHandling, - Logging | undefined, - ModuleManager, - ], - commandDir: string, -) { - const readyOnce$ = fromEvent(client, 'ready').pipe(take(1)); - return readyOnce$ - .pipe( - buildCommandModules(commandDir, sEmitter), - callInitPlugins({ - onStop: module => { - sEmitter.emit( - 'module.register', - SernEmitter.failure(module, SernError.PluginFailure), - ); - }, - onNext: ({ module }) => { - sEmitter.emit('module.register', SernEmitter.success(module)); - return module; - }, - }), - ) - .subscribe(module => { - const result = registerModule(moduleManager, module as Processed); - if (result.err) { - errorHandler.crash(Error(SernError.InvalidModuleType)); - } - }); -} - -function registerModule>( - manager: ModuleManager, - mod: T, -): Result { - const name = mod.name; - const insert = (cb: (ms: ModuleStore) => void) => { - const set = Result.wrap(() => manager.set(cb)); - return set.ok ? ok() : err(); - }; - switch (mod.type) { - case CommandType.Text: { - mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); - return insert(ms => ms.TextCommands.set(name, mod)); - } - case CommandType.Slash: - return insert(ms => - ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod), - ); - case CommandType.Both: { - mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); - return insert(ms => ms.BothCommands.set(name, mod)); - } - case CommandType.CtxUser: - return insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod)); - case CommandType.CtxMsg: - return insert(ms => - ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod), - ); - case CommandType.Button: - return insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod)); - case CommandType.StringSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod)); - case CommandType.MentionableSelect: - return insert(ms => - ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod), - ); - case CommandType.UserSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod)); - case CommandType.ChannelSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.ChannelSelect].set(name, mod)); - case CommandType.RoleSelect: - return insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod)); - case CommandType.Modal: - return insert(ms => ms.ModalSubmit.set(name, mod)); - default: - return err(); - } -} diff --git a/src/handler/events/userDefinedEventsHandling.ts b/src/handler/events/userDefinedEventsHandling.ts deleted file mode 100644 index 9902bb2..0000000 --- a/src/handler/events/userDefinedEventsHandling.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { catchError, finalize, map, mergeAll } from 'rxjs'; -import * as Files from '../module-loading/readFile'; -import type { Dependencies, Processed } from '../../types/handler'; -import { callInitPlugins } from './observableHandling'; -import type { CommandModule, EventModule } from '../../types/module'; -import type { EventEmitter } from 'events'; -import SernEmitter from '../sernEmitter'; -import type { ErrorHandling, Logging } from '../contracts'; -import { SernError, EventType, type Wrapper } from '../structures'; -import { eventDispatcher } from './dispatchers'; -import { handleError } from '../contracts/errorHandling'; -import { errTap, fillDefaults } from './operators'; -import { useContainerRaw } from '../dependencies'; - -export function makeEventsHandler( - [s, client, err, log]: [SernEmitter, EventEmitter, ErrorHandling, Logging | undefined], - eventsPath: string, - containerGetter: Wrapper['containerConfig'], -) { - const lazy = (k: string) => containerGetter.get(k as keyof Dependencies)[0]; - const eventStream$ = eventObservable(eventsPath, s); - - const eventCreation$ = eventStream$.pipe( - map(fillDefaults), - callInitPlugins({ - onStop: module => - s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)), - onNext: ({ module }) => { - s.emit('module.register', SernEmitter.success(module)); - return module; - }, - }), - ); - const intoDispatcher = (e: Processed) => { - switch (e.type) { - case EventType.Sern: - return eventDispatcher(e, s); - case EventType.Discord: - return eventDispatcher(e, client); - case EventType.External: - return eventDispatcher(e, lazy(e.emitter)); - default: - return err.crash( - Error(SernError.InvalidModuleType + ' while creating event handler'), - ); - } - }; - eventCreation$ - .pipe( - map(intoDispatcher), - /** - * Where all events are turned on - */ - mergeAll(), - catchError(handleError(err, log)), - finalize(() => { - log?.info({ message: 'an event module reached end of lifetime' }); - useContainerRaw() - ?.disposeAll() - .then(() => { - log?.info({ message: 'Cleaning container and crashing' }); - }); - }), - ) - .subscribe(); -} - -function eventObservable(events: string, emitter: SernEmitter) { - return Files.buildModuleStream(events).pipe( - errTap(reason => { - emitter.emit('module.register', SernEmitter.failure(undefined, reason)); - }), - ); -} diff --git a/src/handler/module-loading/readFile.ts b/src/handler/module-loading/readFile.ts deleted file mode 100644 index 80e89bc..0000000 --- a/src/handler/module-loading/readFile.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { readdirSync, statSync } from 'fs'; -import { join } from 'path'; -import { type Observable, from, mergeMap } from 'rxjs'; -import { SernError } from '../structures/errors'; -import { type Result, Err, Ok } from 'ts-results-es'; -import { ImportPayload } from '../../types/handler'; -import { pathToFileURL } from 'node:url'; - -// Courtesy @Townsy45 -function readPath(dir: string, arrayOfFiles: string[] = []): string[] { - try { - const files = readdirSync(dir); - for (const file of files) { - if (statSync(dir + '/' + file).isDirectory()) readPath(dir + '/' + file, arrayOfFiles); - else arrayOfFiles.push(join(dir, '/', file)); - } - } catch (err) { - throw err; - } - - return arrayOfFiles; -} -export const fmtFileName = (n: string) => n.substring(0, n.length - 3); -// export const isLazy = (n: string) => n.indexOf(".lazy.", n.length-9) !== -1; - -export async function defaultModuleLoader( - absPath: string, -): Promise, SernError>> { - // prettier-ignore - let module: T | undefined - /// #if MODE === 'esm' - = (await import(pathToFileURL(absPath).toString())).default - /// #elif MODE === 'cjs' - = require(absPath).default; // eslint-disable-line - /// #endif - if (module === undefined) { - return Err(SernError.UndefinedModule); - } - try { - module = new (module as unknown as new () => T)(); - } catch {} - return Ok({ module, absPath }); -} - -/** - * a directory string is converted into a stream of modules. - * starts the stream of modules that sern needs to process on init - * @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files - * @param commandDir - */ -export function buildModuleStream( - commandDir: string, -): Observable, SernError>> { - const commands = getCommands(commandDir); - return from(commands).pipe(mergeMap(defaultModuleLoader)); -} - -export function fullPathFrom(dir: string) { - return join(process.cwd(), dir); -} - -export function getCommands(dir: string): string[] { - return readPath(fullPathFrom(dir)); -} diff --git a/src/handler/plugins/index.ts b/src/handler/plugins/index.ts deleted file mode 100644 index 3f8a662..0000000 --- a/src/handler/plugins/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { EventArgs, InitArgs, CommandArgs } from './args'; -export * from './createPlugin'; diff --git a/src/handler/sern.ts b/src/handler/sern.ts deleted file mode 100644 index b4ac2d1..0000000 --- a/src/handler/sern.ts +++ /dev/null @@ -1,138 +0,0 @@ -import type Wrapper from './structures/wrapper'; -import { makeEventsHandler } from './events/userDefinedEventsHandling'; -import { CommandType, EventType, PluginType } from './structures/enums'; -import type { AnyEventPlugin, ControlPlugin, InitPlugin, Plugin } from '../types/plugin'; -import { makeInteractionCreate } from './events/interactionHandler'; -import { makeReadyEvent } from './events/readyHandler'; -import { makeMessageCreate } from './events/messageHandler'; -import type { - CommandModule, - CommandModuleDefs, - EventModule, - EventModuleDefs, - InputCommand, - InputEvent, -} from '../types/module'; -import type { Dependencies, DependencyConfiguration } from '../types/handler'; -import { composeRoot, makeFetcher, useContainer } from './dependencies/provider'; -import type { Logging } from './contracts'; -import { err, ok, partition } from './utilities/functions'; -import type { Awaitable, ClientEvents } from 'discord.js'; - -/** - * @since 1.0.0 - * @param wrapper Options to pass into sern. - * Function to start the handler up - * @example - * ```ts title="src/index.ts" - * Sern.init({ - * defaultPrefix: '!', - * commands: 'dist/commands', - * events: 'dist/events', - * containerConfig : { - * get: useContainer - * } - * }) - * ``` - */ -export function init(wrapper: Wrapper) { - const logger = wrapper.containerConfig.get('@sern/logger')[0] as Logging | undefined; - const requiredDependenciesAnd = makeFetcher(wrapper); - const startTime = performance.now(); - const { events } = wrapper; - if (events !== undefined) { - makeEventsHandler(requiredDependenciesAnd([]), events, wrapper.containerConfig); - } - const dependencies = requiredDependenciesAnd(['@sern/modules']); - makeReadyEvent(dependencies, wrapper.commands); - makeMessageCreate(dependencies, wrapper.defaultPrefix); - makeInteractionCreate(dependencies); - const endTime = performance.now(); - logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` }); -} - -/** - * @since 1.0.0 - * The object passed into every plugin to control a command's behavior - */ -export const controller = { - next: ok, - stop: err, -}; - -/** - * @since 1.0.0 - * The wrapper function to define command modules for sern - * @param mod - */ -export function commandModule(mod: InputCommand): CommandModule { - const [onEvent, plugins] = partition( - mod.plugins ?? [], - el => (el as Plugin).type === PluginType.Control, - ); - return { - ...mod, - onEvent, - plugins, - } as CommandModule; -} -/** - * @since 1.0.0 - * The wrapper function to define event modules for sern - * @param mod - */ -export function eventModule(mod: InputEvent): EventModule { - const [onEvent, plugins] = partition( - mod.plugins ?? [], - el => (el as Plugin).type === PluginType.Control, - ); - return { - ...mod, - onEvent, - plugins, - } as EventModule; -} - -/** - * Create event modules from discord.js client events, - * This is an {@link eventModule} for discord events, - * where typings can be very bad. - * @param mod - */ -export function discordEvent(mod: { - name: T; - plugins?: AnyEventPlugin[]; - execute: (...args: ClientEvents[T]) => Awaitable; -}) { - return eventModule({ type: EventType.Discord, ...mod }); -} -/** - * @since 2.0.0 - * @param conf a configuration for creating your project dependencies - */ -export function makeDependencies(conf: DependencyConfiguration) { - //Until there are more optional dependencies, just check if the logger exists - composeRoot(conf); - return useContainer(); -} - -/** - * @Experimental - * Will be refactored / changed in future - */ -export abstract class CommandExecutable { - abstract type: Type; - plugins: InitPlugin[] = []; - onEvent: ControlPlugin[] = []; - abstract execute: CommandModuleDefs[Type]['execute']; -} -/** - * @Experimental - * Will be refactored in future - */ -export abstract class EventExecutable { - abstract type: Type; - plugins: InitPlugin[] = []; - onEvent: ControlPlugin[] = []; - abstract execute: EventModuleDefs[Type]['execute']; -} diff --git a/src/handler/structures/errors.ts b/src/handler/structures/errors.ts deleted file mode 100644 index f082485..0000000 --- a/src/handler/structures/errors.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @enum { string } - */ -export enum SernError { - /** - * Throws when registering an invalid module. - * This means it is undefined or an invalid command type was provided - */ - InvalidModuleType = 'Detected an unknown module type', - /** - * Attempted to lookup module in command module store. Nothing was found! - */ - UndefinedModule = `A module could not be detected`, - /** - * Attempted to lookup module in command module store. Nothing was found! - */ - MismatchModule = `A module type mismatched with event emitted!`, - /** - * Unsupported interaction at this moment. - */ - NotSupportedInteraction = `This interaction is not supported.`, - /** - * One plugin called `controller.stop()` (end command execution / loading) - */ - PluginFailure = `A plugin failed to call controller.next()`, - /** - * A crash that occurs when accessing an invalid property of Context - */ - MismatchEvent = `You cannot use message when an interaction fired or vice versa`, - /** - * Unsupported feature attempted to access at this time - */ - NotSupportedYet = `This feature is not supported yet`, - /** - * Required Dependency not found - */ - MissingRequired = `@sern/client is required but was not found`, -} diff --git a/src/handler/structures/index.ts b/src/handler/structures/index.ts deleted file mode 100644 index 917094c..0000000 --- a/src/handler/structures/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Context from './context'; -import type Wrapper from './wrapper'; -import { ModuleStore } from './moduleStore'; -export * from './errors'; -export * from './enums'; -export { Context, Wrapper, ModuleStore }; diff --git a/src/handler/structures/moduleStore.ts b/src/handler/structures/moduleStore.ts deleted file mode 100644 index 856c012..0000000 --- a/src/handler/structures/moduleStore.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { CommandModule } from '../../types/module'; -import { ApplicationCommandType, ComponentType } from 'discord.js'; -import type { Processed } from '../../types/handler'; - -/** - * @since 2.0.0 - * Storing all command modules - * This dependency is usually injected into ModuleManager - */ -export class ModuleStore { - readonly BothCommands = new Map>(); - readonly ApplicationCommands = { - [ApplicationCommandType.User]: new Map>(), - [ApplicationCommandType.Message]: new Map>(), - [ApplicationCommandType.ChatInput]: new Map>(), - }; - readonly ModalSubmit = new Map>(); - readonly TextCommands = new Map>(); - readonly InteractionHandlers = { - [ComponentType.Button]: new Map>(), - [ComponentType.StringSelect]: new Map>(), - [ComponentType.ChannelSelect]: new Map>(), - [ComponentType.MentionableSelect]: new Map>(), - [ComponentType.RoleSelect]: new Map>(), - [ComponentType.UserSelect]: new Map>(), - }; -} diff --git a/src/handler/structures/wrapper.ts b/src/handler/structures/wrapper.ts deleted file mode 100644 index 91514a1..0000000 --- a/src/handler/structures/wrapper.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Dependencies } from '../../types/handler'; - -/** - * @since 1.0.0 - * An object to be passed into Sern#init() function. - * @typedef {object} Wrapper - */ -interface Wrapper { - /** - * @deprecated - * This will be moved to a new field in 3.0.0 - */ - readonly defaultPrefix?: string; - readonly commands: string; - readonly events?: string; - readonly containerConfig: { - get: (...keys: (keyof Dependencies)[]) => unknown[]; - }; -} -export default Wrapper; diff --git a/src/handler/utilities/functions.ts b/src/handler/utilities/functions.ts deleted file mode 100644 index ab67f99..0000000 --- a/src/handler/utilities/functions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as Files from '../module-loading/readFile'; -import { basename } from 'path'; -import { Err, Ok } from 'ts-results-es'; -/** - * A function that returns whatever value is provided. - * Warning: this evaluates { @param value }. It does not defer a value. - * @param value - * @__PURE__ - */ -// prettier-ignore -export const _const = (value: T) => () => value; -/** - * - * @param modName - * @param absPath - */ -export function nameOrFilename(modName: string | undefined, absPath: string) { - return modName ?? Files.fmtFileName(basename(absPath)); -} - -//function wrappers for empty ok / err -export const ok = _const(Ok.EMPTY); -export const err = _const(Err.EMPTY); - -export function partition(arr: (T & V)[], condition: (e: T & V) => boolean): [T[], V[]] { - const t: T[] = []; - const v: V[] = []; - for (const el of arr) { - if (condition(el)) { - t.push(el as T); - } else { - v.push(el as V); - } - } - return [t, v]; -} diff --git a/src/handler/utilities/treeSearch.ts b/src/handler/utilities/treeSearch.ts deleted file mode 100644 index 2fe3c5b..0000000 --- a/src/handler/utilities/treeSearch.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js'; -import type { SernAutocompleteData, SernOptionsData } from '../../types/module'; -import assert from 'assert'; - -/** - * Uses an iterative DFS to check if an autocomplete node exists - * @param iAutocomplete - * @param options - */ -export default function treeSearch( - iAutocomplete: AutocompleteInteraction, - options: SernOptionsData[] | undefined, -): SernAutocompleteData | undefined { - if (options === undefined) return undefined; - //clone to prevent mutation of original command module - const _options = options.map(a => ({...a})); - let subcommands = new Set(); - while (_options.length > 0) { - const cur = _options.pop()!; - switch (cur.type) { - case ApplicationCommandOptionType.Subcommand: - { - subcommands.add(cur.name); - for (const option of cur.options ?? []) - _options.push(option); - } - break; - case ApplicationCommandOptionType.SubcommandGroup: - { - for (const command of cur.options ?? []) - _options.push(command); - } - break; - default: - { - if ('autocomplete' in cur && cur.autocomplete) { - const choice = iAutocomplete.options.getFocused(true); - assert('command' in cur, "No command property found for autocomplete option"); - if(subcommands.size > 0) { - const parent = iAutocomplete.options.getSubcommand(); - const parentAndOptionMatches = subcommands.has(parent) && cur.name === choice.name; - if (parentAndOptionMatches) { - return cur; - } - } else { - if(cur.name === choice.name) { - return cur; - } - } - } - } - break; - } - }} diff --git a/src/handler/events/dispatchers/index.ts b/src/handlers/_internal.ts similarity index 50% rename from src/handler/events/dispatchers/index.ts rename to src/handlers/_internal.ts index b754c3e..4813218 100644 --- a/src/handler/events/dispatchers/index.ts +++ b/src/handlers/_internal.ts @@ -1,2 +1,2 @@ export * from './dispatchers'; -export * from './provideArgs'; +export * from './event-utils'; diff --git a/src/handlers/dispatchers.ts b/src/handlers/dispatchers.ts new file mode 100644 index 0000000..6826cf7 --- /dev/null +++ b/src/handlers/dispatchers.ts @@ -0,0 +1,117 @@ +import { EventEmitter } from 'node:events'; +import * as assert from 'node:assert'; +import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; +import { + arrayifySource, + callPlugin, + isAutocomplete, + treeSearch, + SernError, +} from '../core/_internal'; +import { createResultResolver } from './event-utils'; +import { AutocompleteInteraction, BaseInteraction, Message } from 'discord.js'; +import { CommandType, Context } from '../core'; +import type { Args } from '../types/utility'; +import type { BothCommand, CommandModule, Module, Processed } from '../types/core-modules'; + +function dispatchInteraction( + payload: { module: Processed; event: V }, + createArgs: (m: typeof payload.event) => unknown[], +) { + return { + module: payload.module, + args: createArgs(payload.event), + }; +} +//TODO: refactor dispatchers so that it implements a strategy for each different type of payload? +export function dispatchMessage(module: Processed, args: [Context, Args]) { + return { + module, + args, + }; +} + +function dispatchAutocomplete(payload: { + module: Processed; + event: AutocompleteInteraction; +}) { + const option = treeSearch(payload.event, payload.module.options); + assert.ok( + option, + Error(SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`), + ); + return { + module: option.command as Processed, //autocomplete is not a true "module" warning cast! + args: [payload.event], + }; +} + +export function contextArgs(wrappable: Message | BaseInteraction, messageArgs?: string[]) { + const ctx = Context.wrap(wrappable); + const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.options]; + return [ctx, args] as [Context, Args]; +} + +function interactionArg(interaction: T) { + return [interaction] as [T]; +} + +function intoPayload(module: Processed) { + return pipe( + arrayifySource, + map(args => ({ module, args })), + ); +} + +const createResult = createResultResolver< + Processed, + { module: Processed; args: unknown[] }, + unknown[] +>({ + createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)), + onNext: ({ args }) => args, +}); +/** + * Creates an observable from { source } + * @param module + * @param source + */ +export function eventDispatcher(module: Processed, source: unknown) { + assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); + + const execute: OperatorFunction = concatMap(async args => + module.execute(...args), + ); + return fromEvent(source, module.name).pipe( + intoPayload(module), + concatMap(createResult), + execute, + ); +} + +export function createDispatcher(payload: { + module: Processed; + event: BaseInteraction; +}) { + assert.ok( + CommandType.Text !== payload.module.type, + SernError.MismatchEvent + 'Found text command in interaction stream', + ); + switch (payload.module.type) { + case CommandType.Slash: + case CommandType.Both: { + if (isAutocomplete(payload.event)) { + /** + * Autocomplete is a special case that + * must be handled separately, since it's + * too different from regular command modules + * CAST SAFETY: payload is already guaranteed to be a slash command or both command + */ + return dispatchAutocomplete(payload as never); + } + return dispatchInteraction(payload, contextArgs); + } + default: + return dispatchInteraction(payload, interactionArg); + } +} diff --git a/src/handlers/event-utils.ts b/src/handlers/event-utils.ts new file mode 100644 index 0000000..75935f6 --- /dev/null +++ b/src/handlers/event-utils.ts @@ -0,0 +1,237 @@ +import { Interaction, Message } from 'discord.js'; +import { + EMPTY, + Observable, + concatMap, + filter, + from, + of, + throwError, + tap, + MonoTypeOperatorFunction, + catchError, + finalize, +} from 'rxjs'; +import { + Files, + Id, + callPlugin, + everyPluginOk, + filterMapTo, + handleError, + SernError, + VoidResult, +} from '../core/_internal'; +import { Emitter, ErrorHandling, Logging, ModuleManager, useContainerRaw } from '../core'; +import { contextArgs, createDispatcher, dispatchMessage } from './dispatchers'; +import { ObservableInput, pipe } from 'rxjs'; +import { SernEmitter } from '../core'; +import { Result } from 'ts-results-es'; +import type { Awaitable } from '../types/utility'; +import assert from 'node:assert'; +import type { ControlPlugin } from '../types/core-plugin'; +import type { AnyModule, CommandModule, Module, Processed } from '../types/core-modules'; +import type { ImportPayload } from '../types/core'; + +function createGenericHandler( + source: Observable, + makeModule: (event: Narrowed) => Promise, +) { + return (pred: (i: Source) => i is Narrowed) => source.pipe(filter(pred), concatMap(makeModule)); +} + +/** + * Removes the first character(s) _[depending on prefix length]_ of the message + * @param msg + * @param prefix The prefix to remove + * @returns The message without the prefix + * @example + * message.content = '!ping'; + * console.log(fmt(message, '!')); + * // [ 'ping' ] + */ +export function fmt(msg: string, prefix: string): string[] { + return msg.slice(prefix.length).trim().split(/\s+/g); +} + +/** + * + * Creates an RxJS observable that filters and maps incoming interactions to their respective modules. + * @param i An RxJS observable of interactions. + * @param mg The module manager instance used to retrieve the module path for each interaction. + * @returns A handler to create a RxJS observable of dispatchers that take incoming interactions and execute their corresponding modules. + */ +export function createInteractionHandler( + source: Observable, + mg: ModuleManager, +) { + return createGenericHandler>( + source, + async event => { + const fullPath = mg.get(Id.reconstruct(event)); + assert(fullPath, SernError.UndefinedModule + ' No full path found in module store'); + return Files.defaultModuleLoader>(fullPath).then(payload => + createDispatcher({ module: payload.module, event }), + ); + }, + ); +} + +export function createMessageHandler( + source: Observable, + defaultPrefix: string, + mg: ModuleManager, +) { + return createGenericHandler(source, async event => { + const [prefix, ...rest] = fmt(event.content, defaultPrefix); + const fullPath = mg.get(`${prefix}_A1`); + + assert(fullPath, SernError.UndefinedModule + ' No full path found in module store'); + return Files.defaultModuleLoader>(fullPath).then(payload => { + const args = contextArgs(event, rest); + return dispatchMessage(payload.module, args); + }); + }); +} +/** + * IMPURE SIDE EFFECT + * This function assigns remaining, incomplete data to each imported module. + */ +function assignDefaults( + moduleManager: ModuleManager, +): MonoTypeOperatorFunction> { + return tap(({ module, absPath }) => { + module.name ??= Files.filename(absPath); + module.description ??= '...'; + moduleManager.setMetadata(module, { + isClass: module.constructor.name === 'Function', + fullPath: absPath, + id: Id.create(module.name, module.type), + }); + }); +} + +export function buildModules( + input: ObservableInput, + moduleManager: ModuleManager, +) { + return Files.buildModuleStream>(input).pipe(assignDefaults(moduleManager)); +} + +/** + * Wraps the task in a Result as a try / catch. + * if the task is ok, an event is emitted and the stream becomes empty + * if the task is an error, throw an error down the stream which will be handled by catchError + * @param emitter reference to SernEmitter that will emit a successful execution of module + * @param module the module that will be executed with task + * @param task the deferred execution which will be called + */ +export function executeModule( + emitter: Emitter, + { + module, + task, + }: { + module: Processed; + task: () => Awaitable; + }, +) { + return of(module).pipe( + //converting the task into a promise so rxjs can resolve the Awaitable properly + concatMap(() => Result.wrapAsync(async () => task())), + concatMap(result => { + if (result.ok) { + emitter.emit('module.activate', SernEmitter.success(module)); + return EMPTY; + } else { + return throwError(() => SernEmitter.failure(module, result.val)); + } + }), + ); +} + +/** + * A higher order function that + * - creates a stream of {@link VoidResult} { config.createStream } + * - any failures results to { config.onFailure } being called + * - if all results are ok, the stream is converted to { config.onNext } + * emit config.onSuccess Observable + * @param config + * @returns receiver function for flattening a stream of data + */ +export function createResultResolver< + T extends { execute: (...args: any[]) => any; onEvent: ControlPlugin[] }, + Args extends { module: T; [key: string]: unknown }, + Output, +>(config: { + onStop?: (module: T) => unknown; + onNext: (args: Args) => Output; + createStream: (args: Args) => Observable; +}) { + return (args: Args) => { + const task$ = config.createStream(args); + return task$.pipe( + tap(result => { + result.err && config.onStop?.(args.module); + }), + everyPluginOk, + filterMapTo(() => config.onNext(args)), + ); + }; +} + +/** + * Calls a module's init plugins and checks for Err. If so, call { onStop } and + * ignore the module + */ +export function callInitPlugins>(sernEmitter: Emitter) { + return concatMap( + createResultResolver({ + createStream: args => from(args.module.plugins).pipe(callPlugin(args)), + onStop: (module: T) => { + sernEmitter.emit( + 'module.register', + SernEmitter.failure(module, SernError.PluginFailure), + ); + }, + onNext: ({ module }) => { + sernEmitter.emit('module.register', SernEmitter.success(module)); + return module; + }, + }), + ); +} + +/** + * Creates an executable task ( execute the command ) if all control plugins are successful + * @param onStop emits a failure response to the SernEmitter + */ +export function makeModuleExecutor< + M extends Processed, + Args extends { module: M; args: unknown[] }, +>(onStop: (m: M) => unknown) { + const onNext = ({ args, module }: Args) => ({ + task: () => module.execute(...args), + module, + }); + return concatMap( + createResultResolver({ + onStop, + createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), + onNext, + }), + ); +} + +export const handleCrash = (err: ErrorHandling, log?: Logging) => + pipe( + catchError(handleError(err, log)), + finalize(() => { + log?.info({ + message: 'A stream closed or reached end of lifetime', + }); + useContainerRaw() + ?.disposeAll() + .then(() => log?.info({ message: 'Cleaning container and crashing' })); + }), + ); diff --git a/src/handlers/interaction-event.ts b/src/handlers/interaction-event.ts new file mode 100644 index 0000000..ac79df4 --- /dev/null +++ b/src/handlers/interaction-event.ts @@ -0,0 +1,31 @@ +import { Interaction } from 'discord.js'; +import { concatMap, merge } from 'rxjs'; +import { SernEmitter } from '../core'; +import { + isAutocomplete, + isCommand, + isMessageComponent, + isModal, + sharedEventStream, + SernError, +} from '../core/_internal'; +import { createInteractionHandler, executeModule, makeModuleExecutor } from './_internal'; +import type { DependencyList } from '../types/ioc'; + +export function interactionHandler([emitter, , , modules, client]: DependencyList) { + const interactionStream$ = sharedEventStream(client, 'interactionCreate'); + const handle = createInteractionHandler(interactionStream$, modules); + + const interactionHandler$ = merge( + handle(isMessageComponent), + handle(isAutocomplete), + handle(isCommand), + handle(isModal), + ); + return interactionHandler$.pipe( + makeModuleExecutor(module => { + emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); + }), + concatMap(payload => executeModule(emitter, payload)), + ); +} diff --git a/src/handlers/message-event.ts b/src/handlers/message-event.ts new file mode 100644 index 0000000..90c94d0 --- /dev/null +++ b/src/handlers/message-event.ts @@ -0,0 +1,46 @@ +import { concatMap, EMPTY } from 'rxjs'; +import type { Message } from 'discord.js'; +import { SernEmitter } from '../core'; +import { sharedEventStream, SernError } from '../core/_internal'; +import { createMessageHandler, executeModule, makeModuleExecutor } from './_internal'; +import type { DependencyList } from '../types/ioc'; + +/** + * Ignores messages from any person / bot except itself + * @param prefix + */ +function isNonBot(prefix: string) { + return (msg: Message): msg is Message => !msg.author.bot && hasPrefix(prefix, msg.content); +} + +function hasPrefix(prefix: string, content: string) { + const prefixInContent = content.slice(0, prefix.length); + return ( + prefixInContent.localeCompare(prefix, undefined, { + sensitivity: 'accent', + }) === 0 + ); +} + +export function messageHandler( + [emitter, , log, modules, client]: DependencyList, + defaultPrefix: string | undefined, +) { + if (!defaultPrefix) { + log?.debug({ + message: 'No prefix found. message handler shutting down', + }); + return EMPTY; + } + const messageStream$ = sharedEventStream(client, 'messageCreate'); + const handle = createMessageHandler(messageStream$, defaultPrefix, modules); + + const msgCommands$ = handle(isNonBot(defaultPrefix)); + + return msgCommands$.pipe( + makeModuleExecutor(module => { + emitter.emit('module.activate', SernEmitter.failure(module, SernError.PluginFailure)); + }), + concatMap(payload => executeModule(emitter, payload)), + ); +} diff --git a/src/handlers/ready-event.ts b/src/handlers/ready-event.ts new file mode 100644 index 0000000..b9da8f9 --- /dev/null +++ b/src/handlers/ready-event.ts @@ -0,0 +1,48 @@ +import { ObservableInput, concat, first, fromEvent, ignoreElements, pipe } from 'rxjs'; +import { CommandType } from '../core/structures'; +import { SernError } from '../core/_internal'; +import { Result } from 'ts-results-es'; +import { ModuleManager } from '../core/contracts'; +import { buildModules, callInitPlugins } from './_internal'; +import * as assert from 'node:assert'; +import * as util from 'node:util'; +import type { DependencyList } from '../types/ioc'; +import type { AnyModule, Processed } from '../types/core-modules'; + +export function startReadyEvent( + [sEmitter, , , moduleManager, client]: DependencyList, + allPaths: ObservableInput, +) { + const ready$ = fromEvent(client!, 'ready').pipe(once()); + + return concat(ready$, buildModules(allPaths, moduleManager)) + .pipe(callInitPlugins(sEmitter)) + .subscribe(module => { + register(moduleManager, module).expect( + SernError.InvalidModuleType + ' ' + util.inspect(module), + ); + }); +} + +const once = () => pipe( + first(), + ignoreElements() +) + + +function register>( + manager: ModuleManager, + module: T, +): Result { + const { id, fullPath } = manager.getMetadata(module)!; + + const validModuleType = module.type >= 0 && module.type <= 1 << 10; + assert.ok( + validModuleType, + `Found ${module.name} at ${fullPath}, which does not have a valid type`, + ); + if (module.type === CommandType.Both || module.type === CommandType.Text) { + module.alias?.forEach(a => manager.set(`${a}_A1`, fullPath)); + } + return Result.wrap(() => manager.set(id, fullPath)); +} diff --git a/src/handlers/user-defined-events.ts b/src/handlers/user-defined-events.ts new file mode 100644 index 0000000..822aa73 --- /dev/null +++ b/src/handlers/user-defined-events.ts @@ -0,0 +1,37 @@ +import { ObservableInput, map, mergeAll } from 'rxjs'; +import { EventType } from '../core/structures'; +import { SernError } from '../core/_internal'; +import { buildModules, callInitPlugins, handleCrash, eventDispatcher } from './_internal'; +import { Service } from '../core/ioc'; +import type { DependencyList } from '../types/ioc'; +import type { CommandModule, EventModule, Processed } from '../types/core-modules'; + +export function eventsHandler( + [emitter, err, log, moduleManager, client]: DependencyList, + allPaths: ObservableInput, +) { + //code smell + const intoDispatcher = (e: Processed) => { + switch (e.type) { + case EventType.Sern: + return eventDispatcher(e, emitter); + case EventType.Discord: + return eventDispatcher(e, client); + case EventType.External: + return eventDispatcher(e, Service(e.emitter)); + default: + throw Error(SernError.InvalidModuleType + ' while creating event handler'); + } + }; + buildModules(allPaths, moduleManager) + .pipe( + callInitPlugins(emitter), + map(intoDispatcher), + /** + * Where all events are turned on + */ + mergeAll(), + handleCrash(err, log), + ) + .subscribe(); +} diff --git a/src/index.ts b/src/index.ts index 5715d04..c9ffad7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,53 @@ -import SernEmitter from './handler/sernEmitter'; +export * as Sern from './sern'; +export * from './core'; +export type { + CommandModule, + EventModule, + BothCommand, + ContextMenuMsg, + ContextMenuUser, + SlashCommand, + TextCommand, + ButtonCommand, + StringSelectCommand, + MentionableSelectCommand, + UserSelectCommand, + ChannelSelectCommand, + RoleSelectCommand, + ModalSubmitCommand, + DiscordEventCommand, + SernEventCommand, + ExternalEventCommand, + CommandModuleDefs, + EventModuleDefs, + SernAutocompleteData, + SernOptionsData, + SernSubCommandData, + SernSubCommandGroupData, +} from './types/core-modules'; + +export type { + Controller, + PluginResult, + InitPlugin, + ControlPlugin, + Plugin, + AnyEventPlugin, + AnyCommandPlugin, +} from './types/core-plugin'; + +export type { Wrapper } from './types/core'; + +export type { Args, SlashOptions, Payload, SernEventsMapping } from './types/utility'; + +export type { Singleton, Transient, CoreDependencies, Initializable } from './types/ioc'; + export { - eventModule, commandModule, + eventModule, + discordEvent, EventExecutable, CommandExecutable, - controller, - discordEvent, -} from './handler/sern'; -export * as Sern from './handler/sern'; -export * from './types/handler'; -export * from './types/module'; -export * from './types/plugin'; -export * from './handler/structures'; -export * from './handler/plugins'; -export * from './handler/contracts'; -export { SernEmitter }; -export * from './handler/dependencies'; +} from './core/modules'; + +export { controller } from './sern'; diff --git a/src/sern.ts b/src/sern.ts new file mode 100644 index 0000000..7a6b0d6 --- /dev/null +++ b/src/sern.ts @@ -0,0 +1,75 @@ +import { handleCrash } from './handlers/_internal'; +import { err, ok, Files } from './core/_internal'; +import { merge } from 'rxjs'; +import { Services } from './core/ioc'; +import { Wrapper } from './types/core'; +import { eventsHandler } from './handlers/user-defined-events'; +import { startReadyEvent } from './handlers/ready-event'; +import { messageHandler } from './handlers/message-event'; +import { interactionHandler } from './handlers/interaction-event'; + +/** + * @since 1.0.0 + * @param wrapper Options to pass into sern. + * Function to start the handler up + * @example + * ```ts title="src/index.ts" + * Sern.init({ + * commands: 'dist/commands', + * events: 'dist/events', + * }) + * ``` + */ + +export function init(maybeWrapper: Wrapper | 'file') { + const startTime = performance.now(); + const wrapper = Files.loadConfig(maybeWrapper); + const dependencies = useDependencies(); + const logger = dependencies[2], + errorHandler = dependencies[1]; + const mode = isDevMode(wrapper.mode ?? process.env.MODE); + + if (wrapper.events !== undefined) { + eventsHandler(dependencies, Files.getFullPathTree(wrapper.events, mode)); + } + //Ready event: load all modules and when finished, time should be taken and logged + startReadyEvent(dependencies, Files.getFullPathTree(wrapper.commands, mode)).add(() => { + const time = ((performance.now() - startTime) / 1000).toFixed(2); + dependencies[0].emit('modulesLoaded'); + logger?.info({ + message: `sern: registered all modules in ${time} s`, + }); + }); + + const messages$ = messageHandler(dependencies, wrapper.defaultPrefix); + const interactions$ = interactionHandler(dependencies); + // listening to the message stream and interaction stream + merge(messages$, interactions$).pipe(handleCrash(errorHandler, logger)).subscribe(); +} + +function isDevMode(mode: string | undefined) { + console.info(`Detected mode: "${mode}"`); + if (mode === undefined) { + console.info('No mode found in process.env, assuming DEV'); + } + return mode === 'DEV' || mode == undefined; +} + +function useDependencies() { + return Services( + '@sern/emitter', + '@sern/errors', + '@sern/logger', + '@sern/modules', + '@sern/client', + ); +} + +/** + * @since 1.0.0 + * The object passed into every plugin to control a command's behavior + */ +export const controller = { + next: ok, + stop: err, +}; diff --git a/src/types/module.ts b/src/types/core-modules.ts similarity index 76% rename from src/types/module.ts rename to src/types/core-modules.ts index b5f6dcf..3d77e1c 100644 --- a/src/types/module.ts +++ b/src/types/core-modules.ts @@ -1,34 +1,31 @@ import type { - ApplicationCommandAttachmentOption, - ApplicationCommandChannelOptionData, - ApplicationCommandChoicesData, - ApplicationCommandNonOptionsData, - ApplicationCommandNumericOptionData, - ApplicationCommandOptionData, + APIApplicationCommandBasicOption, + APIApplicationCommandOptionBase, ApplicationCommandOptionType, - ApplicationCommandSubCommandData, - ApplicationCommandSubGroupData, - AutocompleteInteraction, - Awaitable, BaseApplicationCommandOptionsData, + AutocompleteInteraction, ButtonInteraction, + ChannelSelectMenuInteraction, + ClientEvents, + MentionableSelectMenuInteraction, MessageContextMenuCommandInteraction, ModalSubmitInteraction, - UserContextMenuCommandInteraction, - ChannelSelectMenuInteraction, - MentionableSelectMenuInteraction, RoleSelectMenuInteraction, StringSelectMenuInteraction, + UserContextMenuCommandInteraction, UserSelectMenuInteraction, } from 'discord.js'; -import { CommandType } from '../handler/structures/enums'; -import type { Args, SlashOptions } from './handler'; -import type Context from '../handler/structures/context'; -import type { InitPlugin, ControlPlugin } from './plugin'; -import { EventType } from '../handler/structures/enums'; -import type { AnyCommandPlugin, AnyEventPlugin } from './plugin'; -import type { SernEventsMapping } from './handler'; -import type { ClientEvents } from 'discord.js'; +import { CommandType, Context, EventType } from '../../src/core'; +import { AnyCommandPlugin, AnyEventPlugin, ControlPlugin, InitPlugin } from './core-plugin'; +import { Awaitable, Args, SlashOptions, SernEventsMapping } from './utility'; + +export interface CommandMeta { + fullPath: string; + id: string; + isClass: boolean; +} + +export type Processed = T & { name: string; description: string }; export interface Module { type: CommandType | EventType; @@ -36,28 +33,20 @@ export interface Module { onEvent: ControlPlugin[]; plugins: InitPlugin[]; description?: string; - execute: (...args: any[]) => Awaitable; + execute(...args: any[]): Awaitable; } -export interface TextCommand extends Module { - type: CommandType.Text; - alias?: string[]; - execute: (ctx: Context, args: ['text', string[]]) => Awaitable; +export interface SernEventCommand + extends Module { + name?: T; + type: EventType.Sern; + execute(...args: SernEventsMapping[T]): Awaitable; } - -export interface SlashCommand extends Module { - type: CommandType.Slash; - description: string; - options?: SernOptionsData[]; - execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable; -} - -export interface BothCommand extends Module { - type: CommandType.Both; - alias?: string[]; - description: string; - options?: SernOptionsData[]; - execute: (ctx: Context, args: Args) => Awaitable; +export interface ExternalEventCommand extends Module { + name?: string; + emitter: keyof Dependencies; + type: EventType.External; + execute(...args: unknown[]): Awaitable; } export interface ContextMenuUser extends Module { @@ -111,25 +100,31 @@ export interface AutocompleteCommand execute: (ctx: AutocompleteInteraction) => Awaitable; } -export interface SernEventCommand - extends Module { - name?: T; - type: EventType.Sern; - execute(...args: SernEventsMapping[T]): Awaitable; -} - export interface DiscordEventCommand extends Module { name?: T; type: EventType.Discord; execute(...args: ClientEvents[T]): Awaitable; } +export interface TextCommand extends Module { + type: CommandType.Text; + alias?: string[]; + execute: (ctx: Context, args: ['text', string[]]) => Awaitable; +} -export interface ExternalEventCommand extends Module { - name?: string; - emitter: string; - type: EventType.External; - execute(...args: unknown[]): Awaitable; +export interface SlashCommand extends Module { + type: CommandType.Slash; + description: string; + options?: SernOptionsData[]; + execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable; +} + +export interface BothCommand extends Module { + type: CommandType.Both; + alias?: string[]; + description: string; + options?: SernOptionsData[]; + execute: (ctx: Context, args: Args) => Awaitable; } export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand; @@ -148,10 +143,9 @@ export type CommandModule = | ModalSubmitCommand; export type AnyModule = CommandModule | EventModule; - //https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union // Explicit Module Definitions for mapping -export type CommandModuleDefs = { +export interface CommandModuleDefs { [CommandType.Text]: TextCommand; [CommandType.Slash]: SlashCommand; [CommandType.Both]: BothCommand; @@ -164,13 +158,13 @@ export type CommandModuleDefs = { [CommandType.MentionableSelect]: MentionableSelectCommand; [CommandType.UserSelect]: UserSelectCommand; [CommandType.Modal]: ModalSubmitCommand; -}; +} -export type EventModuleDefs = { +export interface EventModuleDefs { [EventType.Sern]: SernEventCommand; [EventType.Discord]: DiscordEventCommand; [EventType.External]: ExternalEventCommand; -}; +} export interface SernAutocompleteData extends Omit { @@ -182,10 +176,10 @@ export interface SernAutocompleteData command: AutocompleteCommand; } -export type CommandModuleNoPlugins = { +type CommandModuleNoPlugins = { [T in CommandType]: Omit; }; -export type EventModulesNoPlugins = { +type EventModulesNoPlugins = { [T in EventType]: Omit; }; @@ -194,35 +188,27 @@ export type InputEvent = { }[EventType]; export type InputCommand = { - [T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: AnyCommandPlugin[] }; + [T in CommandType]: CommandModuleNoPlugins[T] & { + plugins?: AnyCommandPlugin[]; + }; }[CommandType]; /** * Type that replaces autocomplete with {@link SernAutocompleteData} */ -export type BaseOptions = - | ApplicationCommandChoicesData - | ApplicationCommandNonOptionsData - | ApplicationCommandChannelOptionData - | ApplicationCommandNumericOptionData - | ApplicationCommandAttachmentOption +export type SernOptionsData = + | SernSubCommandData + | SernSubCommandGroupData + | APIApplicationCommandBasicOption | SernAutocompleteData; -export interface SernSubCommandData extends BaseApplicationCommandOptionsData { +export interface SernSubCommandData + extends APIApplicationCommandOptionBase { type: ApplicationCommandOptionType.Subcommand; - required?: never; - options?: BaseOptions[]; + options?: SernOptionsData[]; } export interface SernSubCommandGroupData extends BaseApplicationCommandOptionsData { type: ApplicationCommandOptionType.SubcommandGroup; - required?: never; options?: SernSubCommandData[]; } - -export type SernOptionsData = - U extends ApplicationCommandSubCommandData - ? SernSubCommandData - : U extends ApplicationCommandSubGroupData - ? SernSubCommandGroupData - : BaseOptions; diff --git a/src/handler/plugins/args.ts b/src/types/core-plugin.ts similarity index 69% rename from src/handler/plugins/args.ts rename to src/types/core-plugin.ts index 2438d45..fb62699 100644 --- a/src/handler/plugins/args.ts +++ b/src/types/core-plugin.ts @@ -1,42 +1,92 @@ -import type { CommandType } from '../structures/enums'; -import type { PluginType } from '../structures/enums'; -import type { ClientEvents } from 'discord.js'; +/* + * Plugins can be inserted on all commands and are emitted + * + * 1. On ready event, where all commands are loaded. + * 2. On corresponding observable (when command triggers) + * + * The goal of plugins is to organize commands and + * provide extensions to repetitive patterns + * examples include refreshing modules, + * categorizing commands, cool-downs, permissions, etc. + * Plugins are reminiscent of middleware in express. + */ + +import type { Err, Ok, Result } from 'ts-results-es'; import type { BothCommand, ButtonCommand, ChannelSelectCommand, + CommandModule, + ContextMenuMsg, ContextMenuUser, DiscordEventCommand, + EventModule, ExternalEventCommand, MentionableSelectCommand, ModalSubmitCommand, + Module, + Processed, RoleSelectCommand, SernEventCommand, SlashCommand, StringSelectCommand, TextCommand, UserSelectCommand, - ContextMenuMsg, - Module, -} from '../../types/module'; -import type { Args, Payload, Processed, SlashOptions } from '../../types/handler'; -import type Context from '../structures/context'; -import type { MessageContextMenuCommandInteraction } from 'discord.js'; -import type { +} from './core-modules'; +import { Args, Awaitable, Payload, SlashOptions } from './utility'; +import { CommandType, Context, EventType, PluginType } from '../core'; +import { ButtonInteraction, + ChannelSelectMenuInteraction, + ClientEvents, + MentionableSelectMenuInteraction, + MessageContextMenuCommandInteraction, + ModalSubmitInteraction, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserContextMenuCommandInteraction, -} from 'discord.js'; -import type { - ChannelSelectMenuInteraction, - MentionableSelectMenuInteraction, - ModalSubmitInteraction, UserSelectMenuInteraction, } from 'discord.js'; -import { EventType } from '../structures/enums'; -type CommandArgsMatrix = { +export type PluginResult = Awaitable; +export type VoidResult = Result; + +export interface InitArgs> { + module: T; + absPath: string; +} +export interface Controller { + next: () => Ok; + stop: () => Err; +} +export interface Plugin { + type: PluginType; + execute: (...args: Args) => PluginResult; +} + +export interface InitPlugin { + type: PluginType.Init; + execute: (...args: Args) => PluginResult; +} +export interface ControlPlugin { + type: PluginType.Control; + execute: (...args: Args) => PluginResult; +} + +export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; +export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; + +export type CommandArgs< + I extends CommandType = CommandType, + J extends PluginType = PluginType, +> = CommandArgsMatrix[I][J]; + +export type EventArgs< + I extends EventType = EventType, + J extends PluginType = PluginType, +> = EventArgsMatrix[I][J]; + +interface CommandArgsMatrix { [CommandType.Text]: { [PluginType.Control]: [Context, ['text', string[]]]; [PluginType.Init]: [InitArgs>]; @@ -85,9 +135,9 @@ type CommandArgsMatrix = { [PluginType.Control]: [/* library coupled */ ModalSubmitInteraction]; [PluginType.Init]: [InitArgs>]; }; -}; +} -type EventArgsMatrix = { +interface EventArgsMatrix { [EventType.Discord]: { [PluginType.Control]: /* library coupled */ ClientEvents[keyof ClientEvents]; [PluginType.Init]: [InitArgs>]; @@ -100,18 +150,4 @@ type EventArgsMatrix = { [PluginType.Control]: unknown[]; [PluginType.Init]: [InitArgs>]; }; -}; - -export interface InitArgs> { - module: T; - absPath: string; } - -export type CommandArgs< - I extends CommandType = CommandType, - J extends PluginType = PluginType, -> = CommandArgsMatrix[I][J]; -export type EventArgs< - I extends EventType = EventType, - J extends PluginType = PluginType, -> = EventArgsMatrix[I][J]; diff --git a/src/types/core.ts b/src/types/core.ts new file mode 100644 index 0000000..34074c6 --- /dev/null +++ b/src/types/core.ts @@ -0,0 +1,21 @@ +export interface ImportPayload { + module: T; + absPath: string; + [key: string]: unknown; +} + +export interface Wrapper { + commands: string; + defaultPrefix?: string; + events?: string; + /** + * Overload to enable mode in case developer does not use a .env file. + */ + mode?: 'DEV' | 'PROD'; + /* + * @deprecated + */ + containerConfig?: { + get: (...keys: (keyof Dependencies)[]) => unknown[]; + }; +} diff --git a/src/types/dependencies.d.ts b/src/types/dependencies.d.ts new file mode 100644 index 0000000..2290f37 --- /dev/null +++ b/src/types/dependencies.d.ts @@ -0,0 +1,12 @@ +// This file serves an the interface for developers to augment the Dependencies interface +// Developers will have to create a new file dependencies.d.ts in the root directory, augmenting +// this type + +/* eslint-disable @typescript-eslint/consistent-type-imports */ + +import { CoreDependencies } from './ioc'; + +declare global { + interface Dependencies extends CoreDependencies {} + +} diff --git a/src/types/handler.ts b/src/types/handler.ts deleted file mode 100644 index 1648c27..0000000 --- a/src/types/handler.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { CommandInteractionOptionResolver } from 'discord.js'; -import type { PayloadType } from '../handler/structures/enums'; -import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js'; -import type { EventEmitter } from 'events'; -import type { CommandModule, EventModule, AnyModule } from './module'; -import type { UnpackFunction } from 'iti'; -import type { ErrorHandling, Logging, ModuleManager } from '../handler/contracts'; -import type { ModuleStore } from '../handler/structures/moduleStore'; -import type SernEmitter from '../handler/sernEmitter'; -import type { Container } from 'iti'; -// Thanks to @kelsny -export type ParseType = { - [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; -}[keyof T]; - -export type Args = ParseType<{ text: string[]; slash: SlashOptions }>; - -export type SlashOptions = Omit; - -/** - * After modules are transformed, name and description are given default values if none - * are provided to Module. This type represents that transformation - */ -export type AnyDefinedModule = Processed; -export type Payload = - | { type: PayloadType.Success; module: AnyModule } - | { type: PayloadType.Failure; module?: AnyModule; reason: string | Error } - | { type: PayloadType.Warning; reason: string }; -export type SernEventsMapping = { - 'module.register': [Payload]; - 'module.activate': [Payload]; - error: [Payload]; - warning: [Payload]; -}; -export type LogPayload = { message: T }; -export type Singleton = () => T; -export type Transient = () => () => T; - -export interface Dependencies { - '@sern/client': Singleton; - '@sern/logger'?: Singleton; - '@sern/emitter': Singleton; - '@sern/store': Singleton; - '@sern/modules': Singleton; - '@sern/errors': Singleton; -} - -export type ReplyOptions = - | string - | Omit - | MessageReplyOptions; -//prettier-ignore -export type MapDeps = T extends [ - infer First extends keyof Deps, - ...infer Rest extends readonly unknown[], -] - ? [ - UnpackFunction, - ...(MapDeps extends [never] ? [] : MapDeps), - ] - : [never]; -//Basically, '@sern/client' | '@sern/store' | '@sern/modules' | '@sern/error' | '@sern/emitter' will be provided defaults, and you can exclude the rest -export type OptionalDependencies = '@sern/logger'; -export type Processed = T & { name: string; description: string }; -export type Deprecated = [never, Message]; -export interface DependencyConfiguration { - exclude?: Set; - build: (root: Container, {}>) => Container; -} - -export type ImportPayload = { module: T; absPath: string }; diff --git a/src/types/ioc.ts b/src/types/ioc.ts new file mode 100644 index 0000000..bbfb02e --- /dev/null +++ b/src/types/ioc.ts @@ -0,0 +1,48 @@ +import { Container, UnpackFunction } from 'iti'; +import * as Contracts from '../core/contracts'; +/** + * Type to annotate that something is a singleton. + * T is created once and lazily. + */ +export type Singleton = () => T; +/** + * Type to annotate that something is transient. + * Every time this is called, a new object is created + */ +export type Transient = () => () => T; +/** + * Type to annotate that something is initializable. + * If T has an init method, this will be called. + */ +export type Initializable = T + +export type DependencyList = [ + Contracts.Emitter, + Contracts.ErrorHandling, + Contracts.Logging | undefined, + Contracts.ModuleManager, + Contracts.Emitter, +]; + +export interface CoreDependencies { + '@sern/client': () => Contracts.Emitter; + '@sern/logger'?: () => Contracts.Logging; + '@sern/emitter': () => Contracts.Emitter; + '@sern/store': () => Contracts.CoreModuleStore; + '@sern/modules': () => Contracts.ModuleManager; + '@sern/errors': () => Contracts.ErrorHandling; +} + +export type DependencyFromKey = Dependencies[T]; + +export type IntoDependencies = { + [Index in keyof Tuple]: UnpackFunction>>; //Unpack and make NonNullable +} & { length: Tuple['length'] }; + +export interface DependencyConfiguration { + //@deprecated. Loggers will always be included in the future + exclude?: Set<'@sern/logger'>; + build: ( + root: Container, {}>, + ) => Container; +} diff --git a/src/types/plugin.ts b/src/types/plugin.ts deleted file mode 100644 index 502c2c6..0000000 --- a/src/types/plugin.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Plugins can be inserted on all commands and are emitted - * - * 1. On ready event, where all commands are loaded. - * 2. On corresponding observable (when command triggers) - * - * The goal of plugins is to organize commands and - * provide extensions to repetitive patterns - * examples include refreshing modules, - * categorizing commands, cool-downs, permissions, etc. - * Plugins are reminiscent of middleware in express. - */ - -import type { Awaitable } from 'discord.js'; -import type { Err, Ok, Result } from 'ts-results-es'; -import type { PluginType } from '../handler/structures/enums'; -import type { CommandModule, EventModule } from './module'; -import type { CommandArgs, InitArgs } from '../handler/plugins'; -import type { Deprecated, Processed } from './handler'; -import type { CommandType } from '../handler/structures/enums'; -export type PluginResult = Awaitable; -export type VoidResult = Result; - -export interface Controller { - next: () => Ok; - stop: () => Err; -} -export interface Plugin { - type: PluginType; - execute: (...args: Args) => PluginResult; -} - -export interface InitPlugin { - type: PluginType.Init; - execute: (...args: Args) => PluginResult; -} -export interface ControlPlugin { - type: PluginType.Control; - execute: (...args: Args) => PluginResult; -} - -export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; -export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs>]>; - -/** - * @deprecated - * Use the newer helper functions and import { controller } from '@sern/handler' - */ -export interface CommandPlugin { - name?: string; - description?: string; - type: PluginType.Command; - execute: ( - m: InitArgs>, - controller?: Deprecated<'Please import controller instead'>, - ) => PluginResult; -} -/** - * @deprecated - * Use the newer helper functions - */ -export interface EventPlugin { - name?: string; - description?: string; - type: PluginType.Event; - execute: (args: CommandArgs, controller?: Controller) => PluginResult; -} -export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>; -export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>; -export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>; -export type AutocompletePlugin = Deprecated<'Please view alternatives: '>; -export type SernEventPlugin = Deprecated<'Please view alternatives: '>; -export type ExternalEventPlugin = Deprecated<'Please view alternatives: '>; -export type DiscordEventPlugin = Deprecated<'Please view alternatives: '>; diff --git a/src/types/utility.ts b/src/types/utility.ts new file mode 100644 index 0000000..4328e07 --- /dev/null +++ b/src/types/utility.ts @@ -0,0 +1,29 @@ +import { CommandInteractionOptionResolver } from 'discord.js'; +import { PayloadType } from '../core'; +import { AnyModule } from './core-modules'; + +export type Awaitable = PromiseLike | T; + +export type AnyFunction = (...args: unknown[]) => unknown; + +// Thanks to @kelsny +type ParseType = { + [K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never; +}[keyof T]; + +export type SlashOptions = Omit; + +export type Args = ParseType<{ text: string[]; slash: SlashOptions }>; + +export interface SernEventsMapping { + 'module.register': [Payload]; + 'module.activate': [Payload]; + error: [Payload]; + warning: [Payload]; + modulesLoaded: [never?]; +} + +export type Payload = + | { type: PayloadType.Success; module: AnyModule } + | { type: PayloadType.Failure; module?: AnyModule; reason: string | Error } + | { type: PayloadType.Warning; reason: string }; diff --git a/test/core/contracts.test.ts b/test/core/contracts.test.ts new file mode 100644 index 0000000..6d84284 --- /dev/null +++ b/test/core/contracts.test.ts @@ -0,0 +1,16 @@ +import { assertType, describe, it } from 'vitest'; + +import { ModuleStore } from '../../src'; +import * as DefaultContracts from '../../src/core/structures/services'; +import * as Contracts from '../../src/core/contracts/index.js'; + +describe('default contracts', () => { + it('should satisfy contracts', () => { + assertType(new DefaultContracts.DefaultLogging()); + assertType(new DefaultContracts.DefaultErrorHandling()); + assertType( + new DefaultContracts.DefaultModuleManager(new ModuleStore()), + ); + assertType(new ModuleStore()); + }); +}); diff --git a/test/core/create-plugin.test.ts b/test/core/create-plugin.test.ts new file mode 100644 index 0000000..37199e8 --- /dev/null +++ b/test/core/create-plugin.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from 'vitest'; +import { + CommandControlPlugin, + CommandInitPlugin, + EventControlPlugin, + EventInitPlugin, +} from '../../src/core/create-plugins'; +import { PluginType, controller } from '../../src'; + +describe('create-plugins', () => { + it('should make proper control plugins', () => { + const pl = EventControlPlugin(() => controller.next()); + expect(pl).to.have.all.keys(['type', 'execute']); + expect(pl.type).toBe(PluginType.Control); + expect(pl.execute).an('function'); + const pl2 = CommandControlPlugin(() => controller.next()); + expect(pl2).to.have.all.keys(['type', 'execute']); + expect(pl2.type).toBe(PluginType.Control); + expect(pl2.execute).an('function'); + }); + it('should make proper init plugins', () => { + const pl = EventInitPlugin(() => controller.next()); + expect(pl).to.have.all.keys(['type', 'execute']); + expect(pl.type).toBe(PluginType.Init); + expect(pl.execute).an('function'); + + const pl2 = CommandInitPlugin(() => controller.next()); + expect(pl2).to.have.all.keys(['type', 'execute']); + expect(pl2.type).toBe(PluginType.Init); + expect(pl2.execute).an('function'); + }); +}); diff --git a/test/core/functions.test.ts b/test/core/functions.test.ts new file mode 100644 index 0000000..512d34a --- /dev/null +++ b/test/core/functions.test.ts @@ -0,0 +1,404 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { PluginType, SernOptionsData, controller } from '../../src/index'; +import { partitionPlugins, treeSearch } from '../../src/core/functions'; +import { faker } from '@faker-js/faker'; +import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js'; + +vi.mock('discord.js', () => { + const Collection = Map; + const ModalSubmitInteraction = class { + customId; + type = 5; + isModalSubmit = vi.fn(); + constructor(customId) { + this.customId = customId; + } + }; + const ButtonInteraction = class { + customId; + type = 3; + componentType = 2; + isButton = vi.fn(); + constructor(customId) { + this.customId = customId; + } + }; + const AutocompleteInteraction = class { + type = 4; + option: string; + constructor(s: string) { + this.option = s; + } + options = { + getFocused: vi.fn(), + getSubcommand: vi.fn(), + }; + }; + + return { + Collection, + ComponentType: { + Button: 2, + }, + InteractionType: { + Ping: 1, + ApplicationCommand: 2, + MessageComponent: 3, + ApplicationCommandAutocomplete: 4, + ModalSubmit: 5, + }, + ApplicationCommandOptionType: { + Subcommand: 1, + SubcommandGroup: 2, + String: 3, + Integer: 4, + Boolean: 5, + User: 6, + Channel: 7, + Role: 8, + Mentionable: 9, + Number: 10, + Attachment: 11, + }, + ApplicationCommandType: { + ChatInput: 1, + User: 2, + Message: 3, + }, + ModalSubmitInteraction, + ButtonInteraction, + AutocompleteInteraction, + }; +}); + +describe('functions', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + function createRandomPlugins(len: number) { + const random = () => Math.floor(Math.random() * 2) + 1; // 1 or 2, plugin enum + return Array.from({ length: len }, () => ({ + type: random(), + execute: () => (random() === 1 ? controller.next() : controller.stop()), + })); + } + function createRandomChoice() { + return { + type: faker.number.int({ min: 1, max: 11 }), + name: faker.word.noun(), + description: faker.word.adjective(), + }; + } + it('should partition plugins correctly', () => { + const plugins = createRandomPlugins(100); + const [onEvent, init] = partitionPlugins(plugins); + for (const el of onEvent) expect(el.type).to.equal(PluginType.Control); + + for (const el of init) expect(el.type).to.equal(PluginType.Init); + }); + + it('should tree search options tree depth 1', () => { + //@ts-expect-error mocking + let autocmpInteraction = new AutocompleteInteraction('autocomplete'); + const options: SernOptionsData[] = [ + createRandomChoice(), + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'autocomplete', + description: 'here', + autocomplete: true, + command: { onEvent: [], execute: vi.fn() }, + }, + ]; + autocmpInteraction.options.getFocused.mockReturnValue({ + name: 'autocomplete', + value: faker.string.alpha(), + focused: true, + }); + const result = treeSearch(autocmpInteraction, options); + expect(result == undefined).to.be.false; + expect(result.name).to.be.eq('autocomplete'); + expect(result.command).to.be.not.undefined; + }), + it('should tree search depth 2', () => { + //@ts-expect-error mocking + let autocmpInteraction = new AutocompleteInteraction('nested'); + const subcommandName = faker.string.alpha(); + const options: SernOptionsData[] = [ + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName, + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: () => {}, + }, + }, + ], + }, + ]; + autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName); + autocmpInteraction.options.getFocused.mockReturnValue({ + name: 'nested', + value: faker.string.alpha(), + focused: true, + }); + const result = treeSearch(autocmpInteraction, options); + expect(result == undefined).to.be.false; + expect(result.name).to.be.eq('nested'); + expect(result.command).to.be.not.undefined; + }); + + it('should tree search depth n > 2', () => { + //@ts-expect-error mocking + let autocmpInteraction = new AutocompleteInteraction('nested'); + const subcommandName = faker.string.alpha(); + const options: SernOptionsData[] = [ + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName, + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: () => {}, + }, + }, + createRandomChoice(), + ], + }, + ], + }, + ]; + autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName); + autocmpInteraction.options.getFocused.mockReturnValue({ + name: 'nested', + value: faker.string.alpha(), + focused: true, + }); + const result = treeSearch(autocmpInteraction, options); + expect(result == undefined).to.be.false; + expect(result.name).to.be.eq('nested'); + expect(result.command).to.be.not.undefined; + }); + it('should correctly resolve suboption of the same name given two subcommands ', () => { + let autocmpInteraction = new AutocompleteInteraction('nested'); + const subcommandName = faker.string.alpha(); + const options: SernOptionsData[] = [ + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName, + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: () => {}, + }, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName + 'a', + description: faker.string.alpha(), + options: [ + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: () => {}, + }, + }, + ], + }, + ], + }, + ]; + autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName); + autocmpInteraction.options.getFocused.mockReturnValue({ + name: 'nested', + value: faker.string.alpha(), + focused: true, + }); + const result = treeSearch(autocmpInteraction, options); + expect(result).toBeTruthy(); + expect(result.name).to.be.eq('nested'); + expect(result.command).to.be.not.undefined; + }); + it('two subcommands with an option of the same name', () => { + let autocmpInteraction = new AutocompleteInteraction('nested'); + const subcommandName = faker.string.alpha(); + const options: SernOptionsData[] = [ + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName, + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: () => {}, + }, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName + 'a', + description: faker.string.alpha(), + options: [ + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: 'nested', + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: () => {}, + }, + }, + ], + }, + ], + }, + ]; + autocmpInteraction.options.getSubcommand.mockReturnValue(subcommandName); + autocmpInteraction.options.getFocused.mockReturnValue({ + name: 'nested', + value: faker.string.alpha(), + focused: true, + }); + const result = treeSearch(autocmpInteraction, options); + expect(result).toBeTruthy(); + expect(result.name).to.be.eq('nested'); + expect(result.command).to.be.not.undefined; + + let autocmpInteraction2 = new AutocompleteInteraction('nested'); + autocmpInteraction2.options.getSubcommand.mockReturnValue(subcommandName + 'a'); + autocmpInteraction2.options.getFocused.mockReturnValue({ + name: 'nested', + value: faker.string.alpha(), + focused: true, + }); + const result2 = treeSearch(autocmpInteraction2, options); + expect(result2).toBeTruthy(); + expect(result2?.name).toEqual('nested'); + }); + + it('simulates autocomplete typing and resolution', () => { + const subcommandName = faker.string.alpha(); + const optionName = faker.word.noun(); + const options: SernOptionsData[] = [ + { + type: ApplicationCommandOptionType.SubcommandGroup, + name: faker.string.alpha(), + description: faker.string.alpha(), + options: [ + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName, + description: faker.string.alpha(), + options: [ + createRandomChoice(), + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: optionName, + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: vi.fn(), + }, + }, + ], + }, + { + type: ApplicationCommandOptionType.Subcommand, + name: subcommandName + 'a', + description: faker.string.alpha(), + options: [ + createRandomChoice(), + { + type: ApplicationCommandOptionType.String, + name: optionName, + description: faker.string.alpha(), + autocomplete: true, + command: { + onEvent: [], + execute: vi.fn(), + }, + }, + ], + }, + ], + }, + ]; + let accumulator = ''; + let result: unknown; + for (const char of optionName) { + accumulator += char; + + const autocomplete = new AutocompleteInteraction(accumulator); + autocomplete.options.getSubcommand.mockReturnValue(subcommandName); + autocomplete.options.getFocused.mockReturnValue({ + name: accumulator, + value: faker.string.alpha(), + focused: true, + }); + result = treeSearch(autocomplete, options); + } + expect(result).toBeTruthy(); + }); +}); diff --git a/test/core/ioc.test.ts b/test/core/ioc.test.ts new file mode 100644 index 0000000..9413444 --- /dev/null +++ b/test/core/ioc.test.ts @@ -0,0 +1,52 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { CoreContainer } from '../../src/core/ioc/container'; +import { CoreDependencies } from '../../src/core/ioc'; +import { EventEmitter } from 'events'; +import { DefaultLogging, Init, Logging } from '../../src/core'; + +describe('ioc container', () => { + let container: CoreContainer<{}>; + let initDependency: Logging & Init; + beforeEach(() => { + initDependency = { + init: vi.fn(), + error(): void {}, + warning(): void {}, + info(): void {}, + debug(): void {}, + }; + container = new CoreContainer(); + }); + + it('should be ready after calling container.ready()', () => { + container.ready(); + expect(container.isReady()).toBe(true); + }); + it('should container all core dependencies', async () => { + const keys = [ + '@sern/modules', + '@sern/emitter', + '@sern/logger', + '@sern/errors', + ] satisfies (keyof CoreDependencies)[]; + container.add({ + '@sern/logger': () => new DefaultLogging(), + '@sern/client': () => new EventEmitter(), + }); + for (const k of keys) { + //@ts-expect-error typings for iti are strict + expect(() => container.get(k)).not.toThrow(); + } + }); + it('should init modules', () => { + container.upsert({ '@sern/logger': initDependency }); + container.ready(); + expect(initDependency.init).to.toHaveBeenCalledOnce(); + }); + + it('should not lazy module', () => { + container.upsert({ '@sern/logger': () => initDependency }); + container.ready(); + expect(initDependency.init).toHaveBeenCalledTimes(0); + }); +}); diff --git a/test/core/services.test.ts b/test/core/services.test.ts new file mode 100644 index 0000000..c8205ab --- /dev/null +++ b/test/core/services.test.ts @@ -0,0 +1,77 @@ +import { SpyInstance, afterAll, beforeEach, describe, expect, it, vi } from 'vitest'; +import { CoreContainer } from '../../src/core/ioc/container'; +import { DefaultLogging } from '../../src/core'; +import { faker } from '@faker-js/faker'; +import { commandModule } from '../../src'; +import * as Id from '../../src/core/id'; +import { CommandMeta } from '../../src/types/core-modules'; + +describe('services', () => { + //@ts-ignore + let container: CoreContainer; + let consoleMock: SpyInstance; + beforeEach(() => { + container = new CoreContainer(); + container.add({ '@sern/logger': () => new DefaultLogging() }); + container.ready(); + consoleMock = vi.spyOn(container.get('@sern/logger'), 'error').mockImplementation(() => {}); + }); + + afterAll(() => { + consoleMock.mockReset(); + }); + it('module-store.ts', async () => { + function createRandomCommandModules() { + return commandModule({ + type: faker.number.int({ min: 1 << 0, max: 1 << 10 }), + description: faker.string.alpha(), + name: faker.string.alpha(), + execute: () => {}, + }); + } + + const modules = faker.helpers.multiple(createRandomCommandModules, { + count: 40, + }); + + const paths = faker.helpers + .multiple(faker.system.directoryPath, { count: 40 }) + .map((path, i) => `${path}/${modules[i]}.js`); + + const metadata: CommandMeta[] = modules.map((cm, i) => ({ + id: Id.create(cm.name, cm.type), + isClass: false, + fullPath: `${paths[i]}/${cm.name}.js`, + })); + const moduleManager = container.get('@sern/modules'); + let i = 0; + for (const m of modules) { + moduleManager.set(Id.create(m.name, m.type), paths[i]); + moduleManager.setMetadata(m, metadata[i]); + i++; + } + for (const m of modules) { + expect(moduleManager.getMetadata(m), 'module references do not exist').toBeDefined(); + } + }); + + //todo add more + it('error-handling', () => { + const errorHandler = container.get('@sern/errors'); + const lifetime = errorHandler.keepAlive; + for (let i = 0; i < lifetime; i++) { + if (i == lifetime - 1) { + expect(() => errorHandler.updateAlive(new Error('poo'))).toThrowError(); + } else { + expect(() => errorHandler.updateAlive(new Error('poo'))).not.toThrowError(); + } + } + }); + //todo add more, spy on every instance? + it('logger', () => { + container.get('@sern/logger').error({ message: 'error' }); + + expect(consoleMock).toHaveBeenCalledOnce(); + expect(consoleMock).toHaveBeenLastCalledWith({ message: 'error' }); + }); +}); diff --git a/test/handlers/dispatchers.test.ts b/test/handlers/dispatchers.test.ts new file mode 100644 index 0000000..f3ee96e --- /dev/null +++ b/test/handlers/dispatchers.test.ts @@ -0,0 +1,45 @@ +import { beforeEach, describe, expect, vi, it } from 'vitest'; +import { createResultResolver, eventDispatcher } from '../../src/handlers/_internal'; +import { faker } from '@faker-js/faker'; +import { Module } from '../../src/core/types/modules'; +import { Processed } from '../../src/handlers/types'; +import { CommandType } from '../../src/core'; +import { EventEmitter } from 'events'; + +function createRandomModule(): Processed { + return { + type: faker.number.int({ + min: CommandType.Text, + max: CommandType.ChannelSelect, + }), + description: faker.string.alpha(), + name: faker.string.alpha(), + onEvent: [], + plugins: [], + execute: vi.fn(), + }; +} + +describe('eventDispatcher standard', () => { + let m: Processed; + let ee: EventEmitter; + beforeEach(() => { + ee = new EventEmitter(); + m = createRandomModule(); + }); + + it('should throw', () => { + expect(() => eventDispatcher(m, 'not event emitter')).toThrowError(); + }); + it("Shouldn't throw", () => { + expect(() => eventDispatcher(m, ee)).not.toThrowError(); + }); + + it('Should be called once', () => { + const s = eventDispatcher(m, ee); + s.subscribe(); + ee.emit(m.name, faker.string.alpha()); + + expect(m.execute).toHaveBeenCalledOnce(); + }); +}); diff --git a/test/handlers/id.test.ts b/test/handlers/id.test.ts new file mode 100644 index 0000000..e6a2ed8 --- /dev/null +++ b/test/handlers/id.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it, vi } from 'vitest'; +import * as Id from '../../src/core/id'; +import { faker } from '@faker-js/faker'; +import { CommandModule, CommandType, commandModule } from '../../src'; +import { CommandTypeDiscordApi } from '../../src/core/id'; + +function createRandomCommandModules() { + const randomCommandType = [ + CommandType.Text, + CommandType.Both, + CommandType.CtxMsg, + CommandType.CtxUser, + CommandType.Modal, + CommandType.ChannelSelect, + CommandType.RoleSelect, + CommandType.UserSelect, + CommandType.StringSelect, + CommandType.Button, + ]; + return commandModule({ + type: randomCommandType[Math.floor(Math.random() * randomCommandType.length)], + description: faker.string.alpha(), + name: faker.string.alpha(), + execute: () => {}, + }); +} +function createMetadata(c: CommandModule) { + return { + fullPath: faker.system.filePath(), + id: Id.create(c.name, c.type), + isClass: Boolean(Math.floor(Math.random())), + }; +} +const appBitField = 0b000000001111; + +describe('id resolution', () => { + it('should resolve application commands correctly', () => { + const modules = faker.helpers.multiple(createRandomCommandModules, { + count: 20, + }); + const metadata = modules.map(createMetadata); + metadata.forEach((meta, idx) => { + const associatedModule = modules[idx]; + const am = (appBitField & associatedModule.type) !== 0 ? 'A' : 'C'; + let uid = 0; + if ( + associatedModule.type === CommandType.Both || + associatedModule.type === CommandType.Modal + ) { + uid = 1; + } else { + uid = CommandTypeDiscordApi[Math.log2(associatedModule.type)]; + } + expect(meta.id).toBe(associatedModule.name + '_' + am + uid); + }); + }); + + it('maps commands type to discord components or application commands', () => { + expect(CommandTypeDiscordApi[Math.log2(CommandType.Text)]).toBe(1); + + expect(CommandTypeDiscordApi[1]).toBe(1); + expect(CommandTypeDiscordApi[Math.log2(CommandType.CtxUser)]).toBe(2); + expect(CommandTypeDiscordApi[Math.log2(CommandType.CtxMsg)]).toBe(3); + expect(CommandTypeDiscordApi[Math.log2(CommandType.Button)]).toBe(2); + expect(CommandTypeDiscordApi[Math.log2(CommandType.StringSelect)]).toBe(3); + expect(CommandTypeDiscordApi[Math.log2(CommandType.UserSelect)]).toBe(5); + expect(CommandTypeDiscordApi[Math.log2(CommandType.RoleSelect)]).toBe(6); + expect(CommandTypeDiscordApi[Math.log2(CommandType.MentionableSelect)]).toBe(7); + expect(CommandTypeDiscordApi[Math.log2(CommandType.ChannelSelect)]).toBe(8); + expect(CommandTypeDiscordApi[6]).toBe(1); + }); +}); diff --git a/tsconfig-base.json b/tsconfig-base.json deleted file mode 100644 index a6abe9a..0000000 --- a/tsconfig-base.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "noImplicitAny": true, - "experimentalDecorators": true, - "strictNullChecks": true, - "moduleResolution": "node", - "skipLibCheck": true, - "declaration": true, - "preserveSymlinks": true, - "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true - }, - "exclude": ["node_modules", "dist"], - "include": ["src"] -} diff --git a/tsconfig-cjs.json b/tsconfig-cjs.json deleted file mode 100644 index 2c8795f..0000000 --- a/tsconfig-cjs.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "module": "commonjs", - "outDir": "dist/cjs", - "target": "esnext" - } -} diff --git a/tsconfig-esm.json b/tsconfig-esm.json deleted file mode 100644 index fd113f8..0000000 --- a/tsconfig-esm.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig-base.json", - "compilerOptions": { - "module": "esnext", - "outDir": "dist/esm", - "target": "esnext" - } -} diff --git a/tsconfig.json b/tsconfig.json index 9bb4c50..f7535a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,21 @@ { - "extends": "./tsconfig-esm.json" + "compilerOptions": { + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "noImplicitAny": true, + "experimentalDecorators": true, + "strictNullChecks": true, + "moduleResolution": "node", + "skipLibCheck": true, + "declaration": true, + "preserveSymlinks": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "module": "esnext", + "target": "esnext" + }, + "exclude": ["node_modules", "dist"], + "include": ["./src", "./src/**/*.d.ts"] } diff --git a/tsup.config.js b/tsup.config.js index 665b3cb..c9c1a1a 100644 --- a/tsup.config.js +++ b/tsup.config.js @@ -1,48 +1,49 @@ import { defineConfig } from 'tsup'; -import { writeFile } from 'fs/promises'; -import ifdefPlugin from 'esbuild-ifdef'; const shared = { entry: ['src/index.ts'], - external: ['discord.js'], + external: ['discord.js', 'iti'], platform: 'node', clean: true, sourcemap: false, + treeshake: { + moduleSideEffects: false, + correctVarValueBeforeDeclaration: true, //need this to treeshake esm discord.js empty import + annotations: true, + }, }; export default defineConfig([ { - format: 'esm', - target: 'node16', - tsconfig: './tsconfig-esm.json', - outDir: './dist/esm', - treeshake: true, - esbuildPlugins: [ifdefPlugin({ variables: { MODE: 'esm' }, verbose: true })], - outExtension() { - return { - js: '.mjs', - }; - }, - async onSuccess() { - console.log('writing json esm'); - await writeFile('./dist/esm/package.json', JSON.stringify({ type: 'module' })); - }, - ...shared, - }, - { - format: 'cjs', - esbuildPlugins: [ifdefPlugin({ variables: { MODE: 'cjs' }, verbose: true })], - splitting: false, - target: 'node16', - tsconfig: './tsconfig-cjs.json', - outDir: './dist/cjs', - outExtension() { - return { - js: '.cjs', - }; - }, - async onSuccess() { - console.log('writing json commonjs'); - await writeFile('./dist/cjs/package.json', JSON.stringify({ type: 'commonjs' })); - }, + format: ['esm', 'cjs'], + target: 'node18', + tsconfig: './tsconfig.json', + outDir: './dist', + splitting: true, + dts: true, ...shared, }, + // { + // format: 'cjs', + // esbuildPlugins: [ifdefPlugin({ variables: { MODE: 'cjs' }, verbose: true })], + // splitting: false, + // target: 'node18', + // tsconfig: './tsconfig-cjs.json', + // outDir: './dist/cjs', + // outExtension() { + // return { + // js: '.cjs', + // }; + // }, + // async onSuccess() { + // console.log('writing json commonjs'); + // await writeFile('./dist/cjs/package.json', JSON.stringify({ type: 'commonjs' })); + // }, + // ...shared, + // }, + // { + // dts: { + // only: true, + // }, + // entry: ['src/index.ts'], + // outDir: 'dist', + // }, ]); diff --git a/yarn.lock b/yarn.lock index 0af33c8..ddc9323 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 6 cacheKey: 8 +"@aashutoshrathi/word-wrap@npm:^1.2.3": + version: 1.2.6 + resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" + checksum: ada901b9e7c680d190f1d012c84217ce0063d8f5c5a7725bb91ec3c5ed99bb7572680eb2d2938a531ccbaec39a95422fcd8a6b4a13110c7d98dd75402f66a0cd + languageName: node + linkType: hard + "@discordjs/builders@npm:^1.6.3": version: 1.6.3 resolution: "@discordjs/builders@npm:1.6.3" @@ -76,156 +83,310 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/android-arm64@npm:0.17.18" +"@esbuild/android-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm64@npm:0.17.19" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/android-arm@npm:0.17.18" +"@esbuild/android-arm64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/android-arm64@npm:0.18.11" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-arm@npm:0.17.19" conditions: os=android & cpu=arm languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/android-x64@npm:0.17.18" +"@esbuild/android-arm@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/android-arm@npm:0.18.11" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/android-x64@npm:0.17.19" conditions: os=android & cpu=x64 languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/darwin-arm64@npm:0.17.18" +"@esbuild/android-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/android-x64@npm:0.18.11" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-arm64@npm:0.17.19" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/darwin-x64@npm:0.17.18" +"@esbuild/darwin-arm64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/darwin-arm64@npm:0.18.11" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/darwin-x64@npm:0.17.19" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/freebsd-arm64@npm:0.17.18" +"@esbuild/darwin-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/darwin-x64@npm:0.18.11" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-arm64@npm:0.17.19" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/freebsd-x64@npm:0.17.18" +"@esbuild/freebsd-arm64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/freebsd-arm64@npm:0.18.11" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/freebsd-x64@npm:0.17.19" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-arm64@npm:0.17.18" +"@esbuild/freebsd-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/freebsd-x64@npm:0.18.11" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm64@npm:0.17.19" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-arm@npm:0.17.18" +"@esbuild/linux-arm64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-arm64@npm:0.18.11" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-arm@npm:0.17.19" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-ia32@npm:0.17.18" +"@esbuild/linux-arm@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-arm@npm:0.18.11" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ia32@npm:0.17.19" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-loong64@npm:0.17.18" +"@esbuild/linux-ia32@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-ia32@npm:0.18.11" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-loong64@npm:0.17.19" conditions: os=linux & cpu=loong64 languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-mips64el@npm:0.17.18" +"@esbuild/linux-loong64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-loong64@npm:0.18.11" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-mips64el@npm:0.17.19" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-ppc64@npm:0.17.18" +"@esbuild/linux-mips64el@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-mips64el@npm:0.18.11" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-ppc64@npm:0.17.19" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-riscv64@npm:0.17.18" +"@esbuild/linux-ppc64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-ppc64@npm:0.18.11" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-riscv64@npm:0.17.19" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-s390x@npm:0.17.18" +"@esbuild/linux-riscv64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-riscv64@npm:0.18.11" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-s390x@npm:0.17.19" conditions: os=linux & cpu=s390x languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-x64@npm:0.17.18" +"@esbuild/linux-s390x@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-s390x@npm:0.18.11" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/linux-x64@npm:0.17.19" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/netbsd-x64@npm:0.17.18" +"@esbuild/linux-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/linux-x64@npm:0.18.11" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/netbsd-x64@npm:0.17.19" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/openbsd-x64@npm:0.17.18" +"@esbuild/netbsd-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/netbsd-x64@npm:0.18.11" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/openbsd-x64@npm:0.17.19" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/sunos-x64@npm:0.17.18" +"@esbuild/openbsd-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/openbsd-x64@npm:0.18.11" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/sunos-x64@npm:0.17.19" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/win32-arm64@npm:0.17.18" +"@esbuild/sunos-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/sunos-x64@npm:0.18.11" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-arm64@npm:0.17.19" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/win32-ia32@npm:0.17.18" +"@esbuild/win32-arm64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/win32-arm64@npm:0.18.11" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-ia32@npm:0.17.19" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/win32-x64@npm:0.17.18" +"@esbuild/win32-ia32@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/win32-ia32@npm:0.18.11" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.17.19": + version: 0.17.19 + resolution: "@esbuild/win32-x64@npm:0.17.19" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.18.11": + version: 0.18.11 + resolution: "@esbuild/win32-x64@npm:0.18.11" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -242,26 +403,26 @@ __metadata: linkType: hard "@eslint-community/regexpp@npm:^4.4.0": - version: 4.5.0 - resolution: "@eslint-community/regexpp@npm:4.5.0" - checksum: 99c01335947dbd7f2129e954413067e217ccaa4e219fe0917b7d2bd96135789384b8fedbfb8eb09584d5130b27a7b876a7150ab7376f51b3a0c377d5ce026a10 + version: 4.5.1 + resolution: "@eslint-community/regexpp@npm:4.5.1" + checksum: 6d901166d64998d591fab4db1c2f872981ccd5f6fe066a1ad0a93d4e11855ecae6bfb76660869a469563e8882d4307228cebd41142adb409d182f2966771e57e languageName: node linkType: hard "@eslint/eslintrc@npm:^2.0.2": - version: 2.0.2 - resolution: "@eslint/eslintrc@npm:2.0.2" + version: 2.1.0 + resolution: "@eslint/eslintrc@npm:2.1.0" dependencies: ajv: ^6.12.4 debug: ^4.3.2 - espree: ^9.5.1 + espree: ^9.6.0 globals: ^13.19.0 ignore: ^5.2.0 import-fresh: ^3.2.1 js-yaml: ^4.1.0 minimatch: ^3.1.2 strip-json-comments: ^3.1.1 - checksum: cfcf5e12c7b2c4476482e7f12434e76eae16fcd163ee627309adb10b761e5caa4a4e52ed7be464423320ff3d11eca5b50de5bf8be3e25834222470835dd5c801 + checksum: d5ed0adbe23f6571d8c9bb0ca6edf7618dc6aed4046aa56df7139f65ae7b578874e0d9c796df784c25bda648ceb754b6320277d828c8b004876d7443b8dc018c languageName: node linkType: hard @@ -272,21 +433,21 @@ __metadata: languageName: node linkType: hard -"@gar/promisify@npm:^1.1.3": - version: 1.1.3 - resolution: "@gar/promisify@npm:1.1.3" - checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 +"@faker-js/faker@npm:^8.0.1": + version: 8.0.2 + resolution: "@faker-js/faker@npm:8.0.2" + checksum: cf73daf9a50397eb0b3f04a7ef3fbb6f54acc026b167707136a7e58a517fd805a46c9e8c5f017f3022f104a4ff984765daaeee2cb98b3c34646db05c2acad441 languageName: node linkType: hard "@humanwhocodes/config-array@npm:^0.11.8": - version: 0.11.8 - resolution: "@humanwhocodes/config-array@npm:0.11.8" + version: 0.11.10 + resolution: "@humanwhocodes/config-array@npm:0.11.10" dependencies: "@humanwhocodes/object-schema": ^1.2.1 debug: ^4.1.1 minimatch: ^3.0.5 - checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3 + checksum: 1b1302e2403d0e35bc43e66d67a2b36b0ad1119efc704b5faff68c41f791a052355b010fb2d27ef022670f550de24cd6d08d5ecf0821c16326b7dcd0ee5d5d8a languageName: node linkType: hard @@ -304,6 +465,29 @@ __metadata: languageName: node linkType: hard +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: ^5.1.2 + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: ^7.0.1 + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: ^8.1.0 + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 4a473b9b32a7d4d3cfb7a614226e555091ff0c5a29a1734c28c72a182c2f6699b26fc6b5c2131dfd841e86b185aea714c72201d7c98c2fba5f17709333a67aeb + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.0": + version: 29.6.0 + resolution: "@jest/schemas@npm:29.6.0" + dependencies: + "@sinclair/typebox": ^0.27.8 + checksum: c00511c69cf89138a7d974404d3a5060af375b5a52b9c87215d91873129b382ca11c1ff25bd6d605951404bb381ddce5f8091004a61e76457da35db1f5c51365 + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.3 resolution: "@jridgewell/gen-mapping@npm:0.3.3" @@ -336,7 +520,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.15": version: 1.4.15 resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 @@ -380,23 +564,19 @@ __metadata: languageName: node linkType: hard -"@npmcli/fs@npm:^2.1.0": - version: 2.1.2 - resolution: "@npmcli/fs@npm:2.1.2" +"@npmcli/fs@npm:^3.1.0": + version: 3.1.0 + resolution: "@npmcli/fs@npm:3.1.0" dependencies: - "@gar/promisify": ^1.1.3 semver: ^7.3.5 - checksum: 405074965e72d4c9d728931b64d2d38e6ea12066d4fad651ac253d175e413c06fe4350970c783db0d749181da8fe49c42d3880bd1cbc12cd68e3a7964d820225 + checksum: a50a6818de5fc557d0b0e6f50ec780a7a02ab8ad07e5ac8b16bf519e0ad60a144ac64f97d05c443c3367235d337182e1d012bbac0eb8dbae8dc7b40b193efd0e languageName: node linkType: hard -"@npmcli/move-file@npm:^2.0.0": - version: 2.0.1 - resolution: "@npmcli/move-file@npm:2.0.1" - dependencies: - mkdirp: ^1.0.4 - rimraf: ^3.0.2 - checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 6ad6a00fc4f2f2cfc6bff76fb1d88b8ee20bc0601e18ebb01b6d4be583733a860239a521a7fbca73b612e66705078809483549d2b18f370eb346c5155c8e4a0f languageName: node linkType: hard @@ -408,19 +588,19 @@ __metadata: linkType: hard "@sapphire/shapeshift@npm:^3.8.2": - version: 3.8.2 - resolution: "@sapphire/shapeshift@npm:3.8.2" + version: 3.9.2 + resolution: "@sapphire/shapeshift@npm:3.9.2" dependencies: fast-deep-equal: ^3.1.3 lodash: ^4.17.21 - checksum: 5b2b26ad13a4c8c4b03a9500541df6c070776cf0a49cb7c752df147f63afc76976bc0d4c6f5977cf2597062b810557d7069e99c8e039bf22bf54d5d88da58de5 + checksum: 0d4572281a2a43dc444f56aef7462d16fdc49cdf0e625d521bfeae4b2219e35b53b7752b4e7396e402ce3b1a21c86afc4c3c82ce1547822a6e844116bb220760 languageName: node linkType: hard "@sapphire/snowflake@npm:^3.4.2": - version: 3.4.2 - resolution: "@sapphire/snowflake@npm:3.4.2" - checksum: 3bcd05608a63f012538aa0a45bc6abe5b19e15979a1012a9b3af1e0901f897e4df83c80d484c87bd1dece1124f5a14a2f86427190b1dca7f116ee34f13c96788 + version: 3.5.1 + resolution: "@sapphire/snowflake@npm:3.5.1" + checksum: 8fc025020adab1a7a1a5d2cf07704d598cc1977b50e5fcd3a5dd239f00934dc936d3a4d5ae336e71d8bf1d88ec27aa814b34de79e38ff097b7b9ba5a7977a683 languageName: node linkType: hard @@ -428,12 +608,13 @@ __metadata: version: 0.0.0-use.local resolution: "@sern/handler@workspace:." dependencies: + "@faker-js/faker": ^8.0.1 "@types/node": ^18.15.11 "@typescript-eslint/eslint-plugin": 5.58.0 "@typescript-eslint/parser": 5.59.1 - discord.js: ^14.9.0 + dependency-cruiser: ^13.0.5 + discord.js: 14.11.0 esbuild: ^0.17.0 - esbuild-ifdef: ^0.2.0 eslint: 8.39.0 iti: ^0.6.0 prettier: 2.8.8 @@ -441,9 +622,17 @@ __metadata: ts-results-es: ^3.6.0 tsup: ^6.7.0 typescript: 5.0.2 + vitest: latest languageName: unknown linkType: soft +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 + languageName: node + linkType: hard + "@tokenizer/token@npm:^0.3.0": version: 0.3.0 resolution: "@tokenizer/token@npm:0.3.0" @@ -458,40 +647,56 @@ __metadata: languageName: node linkType: hard +"@types/chai-subset@npm:^1.3.3": + version: 1.3.3 + resolution: "@types/chai-subset@npm:1.3.3" + dependencies: + "@types/chai": "*" + checksum: 4481da7345022995f5a105e6683744f7203d2c3d19cfe88d8e17274d045722948abf55e0adfd97709e0f043dade37a4d4e98cd4c660e2e8a14f23e6ecf79418f + languageName: node + linkType: hard + +"@types/chai@npm:*, @types/chai@npm:^4.3.5": + version: 4.3.5 + resolution: "@types/chai@npm:4.3.5" + checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 + languageName: node + linkType: hard + "@types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 00239e97234eeb5ceefb0c1875d98ade6e922bfec39dd365ec6bd360b5c2f825e612ac4f6e5f1d13601b8b30f378f15e6faa805a3a732f4a1bbe61915163d293 languageName: node linkType: hard "@types/node@npm:*": - version: 20.1.2 - resolution: "@types/node@npm:20.1.2" - checksum: 9bb2cf0a846e60cc06c44a5622c1998d8373c8675b24d11c86845a29f32c60f5bca9627f2a2535640f902be3dbcc60f126a895abe8e10f12e1467ae3deaba56d + version: 20.4.1 + resolution: "@types/node@npm:20.4.1" + checksum: 22cbcc792f2eb636fe4188778ed0f32658ab872aa7fcb9847b3fa289a42b14b9f5e30c6faec50ef3c7adbc6c2a246926e5858136bb8b10c035a3fcaa6afbeed2 languageName: node linkType: hard "@types/node@npm:^18.15.11": - version: 18.16.7 - resolution: "@types/node@npm:18.16.7" - checksum: dbff7d41a050557cc81aa37cb186aaf3a799b3001bc4bb1015b240398abde1f9d0ac1e943959bba7f2ab2701292ae407a9ee2fc0f735a05b28bfc97d0b2b5e91 + version: 18.16.19 + resolution: "@types/node@npm:18.16.19" + checksum: 63c31f09616508aa7135380a4c79470a897b75f9ff3a70eb069e534dfabdec3f32fb0f9df5939127f1086614d980ddea0fa5e8cc29a49103c4f74cd687618aaf languageName: node linkType: hard "@types/semver@npm:^7.3.12": - version: 7.3.13 - resolution: "@types/semver@npm:7.3.13" - checksum: 00c0724d54757c2f4bc60b5032fe91cda6410e48689633d5f35ece8a0a66445e3e57fa1d6e07eb780f792e82ac542948ec4d0b76eb3484297b79bd18b8cf1cb0 + version: 7.5.0 + resolution: "@types/semver@npm:7.5.0" + checksum: 0a64b9b9c7424d9a467658b18dd70d1d781c2d6f033096a6e05762d20ebbad23c1b69b0083b0484722aabf35640b78ccc3de26368bcae1129c87e9df028a22e2 languageName: node linkType: hard "@types/ws@npm:^8.5.4": - version: 8.5.4 - resolution: "@types/ws@npm:8.5.4" + version: 8.5.5 + resolution: "@types/ws@npm:8.5.5" dependencies: "@types/node": "*" - checksum: fefbad20d211929bb996285c4e6f699b12192548afedbe4930ab4384f8a94577c9cd421acaad163cacd36b88649509970a05a0b8f20615b30c501ed5269038d1 + checksum: d00bf8070e6938e3ccf933010921c6ce78ac3606696ce37a393b27a9a603f7bd93ea64f3c5fa295a2f743575ba9c9a9fdb904af0f5fe2229bf2adf0630386e4a languageName: node linkType: hard @@ -661,10 +866,63 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:0.33.0": + version: 0.33.0 + resolution: "@vitest/expect@npm:0.33.0" + dependencies: + "@vitest/spy": 0.33.0 + "@vitest/utils": 0.33.0 + chai: ^4.3.7 + checksum: da6bf8e4a4f23218088b4e7dcdf6eb9f8d92e82a98a674edf8be2f333625179da6802936a948e7a60e0918da53e7ec548183d1d9d42f0e1c4e2d3f66fd63e11f + languageName: node + linkType: hard + +"@vitest/runner@npm:0.33.0": + version: 0.33.0 + resolution: "@vitest/runner@npm:0.33.0" + dependencies: + "@vitest/utils": 0.33.0 + p-limit: ^4.0.0 + pathe: ^1.1.1 + checksum: de731aa0687cf15f141e81fb11027ff52860292f6d8957678c9fcd307502e4f9fd679bcaff93b53d29eeeb694d403d6aa52d49d341f998ec2b794e7abe061572 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:0.33.0": + version: 0.33.0 + resolution: "@vitest/snapshot@npm:0.33.0" + dependencies: + magic-string: ^0.30.1 + pathe: ^1.1.1 + pretty-format: ^29.5.0 + checksum: ff2604d5bf09342eab45109df06f4e2e9e78698bf26b0eed1f4871d7757312e43de90ead938698be3e03e9873d4081ebeb69c94928b8065c53d1e9f28742185e + languageName: node + linkType: hard + +"@vitest/spy@npm:0.33.0": + version: 0.33.0 + resolution: "@vitest/spy@npm:0.33.0" + dependencies: + tinyspy: ^2.1.1 + checksum: 501a704a10b411f407fbcedeaf1f469e6fcac4894af11fa89c74e6f64bf3eebbcd006cf86377ae379708c0b8c860243db504f5d4e90d382419aa666458b76800 + languageName: node + linkType: hard + +"@vitest/utils@npm:0.33.0": + version: 0.33.0 + resolution: "@vitest/utils@npm:0.33.0" + dependencies: + diff-sequences: ^29.4.3 + loupe: ^2.3.6 + pretty-format: ^29.5.0 + checksum: 8c5b381f5599ca517bedd0e46805e91b1150564473d37b2b80ef45aa9c16cb59d296513dd34bc2171904beb28be73b89e5333056539d49a0ba9d513ae7672a0a + languageName: node + linkType: hard + "@vladfrangu/async_event_emitter@npm:^2.2.1": - version: 2.2.1 - resolution: "@vladfrangu/async_event_emitter@npm:2.2.1" - checksum: d56c6c9dc0588f5632e5d6ed373fcbcdf5fe6de6cf51dadea86899701fcf53631f41f7473b1f5d9da300bbca20ab9cd192c5c436bb77ce9df9aa60ff59a7b0f2 + version: 2.2.2 + resolution: "@vladfrangu/async_event_emitter@npm:2.2.2" + checksum: ed948294fea1a2dc8b8f307f4061bf65e2043a946132f288702f0572a806ebe3123b8c7e522e70d2abbd3616f5d67027c9e59df9ef80b0195f7502a848a426ba languageName: node linkType: hard @@ -675,7 +933,14 @@ __metadata: languageName: node linkType: hard -"acorn-jsx@npm:^5.3.2": +"acorn-jsx-walk@npm:2.0.0": + version: 2.0.0 + resolution: "acorn-jsx-walk@npm:2.0.0" + checksum: 49417eab5bb595c1cdc96d64205eda7cc58f32d655068826ba96ec7a4dc4f957d55ca9e62a299d0dbdeae71c6dc518be1378c21d3b720e741812f55f2a13c937 + languageName: node + linkType: hard + +"acorn-jsx@npm:5.3.2, acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" peerDependencies: @@ -684,12 +949,37 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.8.0": - version: 8.8.2 - resolution: "acorn@npm:8.8.2" +"acorn-loose@npm:8.3.0": + version: 8.3.0 + resolution: "acorn-loose@npm:8.3.0" + dependencies: + acorn: ^8.5.0 + checksum: 3418a20bded1e74a20950dee8289fb87808c21a50d4065e4ec48230668ea77f4238be1dd1ee30b2116f469e496bcdaf937ccb86d469482e028052f8eec804c07 + languageName: node + linkType: hard + +"acorn-walk@npm:8.2.0, acorn-walk@npm:^8.2.0": + version: 8.2.0 + resolution: "acorn-walk@npm:8.2.0" + checksum: 1715e76c01dd7b2d4ca472f9c58968516a4899378a63ad5b6c2d668bba8da21a71976c14ec5f5b75f887b6317c4ae0b897ab141c831d741dc76024d8745f1ad1 + languageName: node + linkType: hard + +"acorn@npm:8.9.0": + version: 8.9.0 + resolution: "acorn@npm:8.9.0" bin: acorn: bin/acorn - checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + checksum: 25dfb94952386ecfb847e61934de04a4e7c2dc21c2e700fc4e2ef27ce78cb717700c4c4f279cd630bb4774948633c3859fc16063ec8573bda4568e0a312e6744 + languageName: node + linkType: hard + +"acorn@npm:^8.5.0, acorn@npm:^8.8.2, acorn@npm:^8.9.0": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" + bin: + acorn: bin/acorn + checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d languageName: node linkType: hard @@ -723,6 +1013,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:8.12.0": + version: 8.12.0 + resolution: "ajv@npm:8.12.0" + dependencies: + fast-deep-equal: ^3.1.1 + json-schema-traverse: ^1.0.0 + require-from-string: ^2.0.2 + uri-js: ^4.2.2 + checksum: 4dc13714e316e67537c8b31bc063f99a1d9d9a497eb4bbd55191ac0dcd5e4985bbb71570352ad6f1e76684fb6d790928f96ba3b2d4fd6e10024be9612fe3f001 + languageName: node + linkType: hard + "ajv@npm:^6.10.0, ajv@npm:^6.12.4": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -742,7 +1044,14 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^4.1.0": +"ansi-regex@npm:^6.0.1": + version: 6.0.1 + resolution: "ansi-regex@npm:6.0.1" + checksum: 1ff8b7667cded1de4fa2c9ae283e979fc87036864317da86a2e546725f96406746411d0d85e87a2d12fa5abd715d90006de7fa4fa0477c92321ad3b4c7d4e169 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": version: 4.3.0 resolution: "ansi-styles@npm:4.3.0" dependencies: @@ -751,6 +1060,20 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: ef940f2f0ced1a6347398da88a91da7930c33ecac3c77b72c5905f8b8fe402c52e6fde304ff5347f616e27a742da3f1dc76de98f6866c69251ad0b07a66776d9 + languageName: node + linkType: hard + "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -799,6 +1122,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -861,36 +1191,30 @@ __metadata: languageName: node linkType: hard -"cac@npm:^6.7.12": +"cac@npm:^6.7.12, cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" checksum: 45a2496a9443abbe7f52a49b22fbe51b1905eff46e03fd5e6c98e3f85077be3f8949685a1849b1a9cd2bc3e5567dfebcf64f01ce01847baf918f1b37c839791a languageName: node linkType: hard -"cacache@npm:^16.1.0": - version: 16.1.3 - resolution: "cacache@npm:16.1.3" +"cacache@npm:^17.0.0": + version: 17.1.3 + resolution: "cacache@npm:17.1.3" dependencies: - "@npmcli/fs": ^2.1.0 - "@npmcli/move-file": ^2.0.0 - chownr: ^2.0.0 - fs-minipass: ^2.1.0 - glob: ^8.0.1 - infer-owner: ^1.0.4 + "@npmcli/fs": ^3.1.0 + fs-minipass: ^3.0.0 + glob: ^10.2.2 lru-cache: ^7.7.1 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-collect: ^1.0.2 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 - mkdirp: ^1.0.4 p-map: ^4.0.0 - promise-inflight: ^1.0.1 - rimraf: ^3.0.2 - ssri: ^9.0.0 + ssri: ^10.0.0 tar: ^6.1.11 - unique-filename: ^2.0.0 - checksum: d91409e6e57d7d9a3a25e5dcc589c84e75b178ae8ea7de05cbf6b783f77a5fae938f6e8fda6f5257ed70000be27a681e1e44829251bfffe4c10216002f8f14e6 + unique-filename: ^3.0.0 + checksum: 385756781e1e21af089160d89d7462b7ed9883c978e848c7075b90b73cb823680e66092d61513050164588387d2ca87dd6d910e28d64bc13a9ac82cd8580c796 languageName: node linkType: hard @@ -901,7 +1225,29 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0": +"chai@npm:^4.3.7": + version: 4.3.7 + resolution: "chai@npm:4.3.7" + dependencies: + assertion-error: ^1.1.0 + check-error: ^1.0.2 + deep-eql: ^4.1.2 + get-func-name: ^2.0.0 + loupe: ^2.3.1 + pathval: ^1.1.1 + type-detect: ^4.0.5 + checksum: 0bba7d267848015246a66995f044ce3f0ebc35e530da3cbdf171db744e14cbe301ab913a8d07caf7952b430257ccbb1a4a983c570a7c5748dc537897e5131f7c + languageName: node + linkType: hard + +"chalk@npm:5.3.0": + version: 5.3.0 + resolution: "chalk@npm:5.3.0" + checksum: 623922e077b7d1e9dedaea6f8b9e9352921f8ae3afe739132e0e00c275971bdd331268183b2628cf4ab1727c45ea1f28d7e24ac23ce1db1eb653c414ca8a5a80 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -911,6 +1257,13 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.2": + version: 1.0.2 + resolution: "check-error@npm:1.0.2" + checksum: d9d106504404b8addd1ee3f63f8c0eaa7cd962a1a28eb9c519b1c4a1dc7098be38007fc0060f045ee00f075fbb7a2a4f42abcf61d68323677e11ab98dc16042e + languageName: node + linkType: hard + "chokidar@npm:^3.5.1": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -969,6 +1322,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:11.0.0": + version: 11.0.0 + resolution: "commander@npm:11.0.0" + checksum: 6621954e1e1d078b4991c1f5bbd9439ad37aa7768d6ab4842de1dbd4d222c8a27e1b8e62108b3a92988614af45031d5bb2a2aaa92951f4d0c934d1a1ac564bb4 + languageName: node + linkType: hard + "commander@npm:^4.0.0": version: 4.1.1 resolution: "commander@npm:4.1.1" @@ -990,7 +1350,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" dependencies: @@ -1013,6 +1373,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.2": + version: 4.1.3 + resolution: "deep-eql@npm:4.1.3" + dependencies: + type-detect: ^4.0.0 + checksum: 7f6d30cb41c713973dc07eaadded848b2ab0b835e518a88b91bea72f34e08c4c71d167a722a6f302d3a6108f05afd8e6d7650689a84d5d29ec7fe6220420397f + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -1034,6 +1403,55 @@ __metadata: languageName: node linkType: hard +"dependency-cruiser@npm:^13.0.5": + version: 13.0.5 + resolution: "dependency-cruiser@npm:13.0.5" + dependencies: + acorn: 8.9.0 + acorn-jsx: 5.3.2 + acorn-jsx-walk: 2.0.0 + acorn-loose: 8.3.0 + acorn-walk: 8.2.0 + ajv: 8.12.0 + chalk: 5.3.0 + commander: 11.0.0 + enhanced-resolve: 5.15.0 + figures: 5.0.0 + glob: 10.3.1 + handlebars: 4.7.7 + ignore: 5.2.4 + indent-string: 5.0.0 + interpret: ^3.1.1 + is-installed-globally: 0.4.0 + json5: 2.2.3 + lodash: 4.17.21 + prompts: 2.4.2 + rechoir: ^0.8.0 + safe-regex: 2.1.1 + semver: ^7.5.3 + semver-try-require: 6.2.3 + teamcity-service-messages: 0.1.14 + tsconfig-paths-webpack-plugin: 4.0.1 + watskeburt: 0.11.5 + wrap-ansi: 8.1.0 + bin: + depcruise: bin/dependency-cruise.mjs + depcruise-baseline: bin/depcruise-baseline.mjs + depcruise-fmt: bin/depcruise-fmt.mjs + depcruise-wrap-stream-in-html: bin/wrap-stream-in-html.mjs + dependency-cruise: bin/dependency-cruise.mjs + dependency-cruiser: bin/dependency-cruise.mjs + checksum: 0cba1516a0c46ad04027103533b0aeb4739f56d7ef9722932e76d9a0ead5337ae6940e10e44111f14f9a7a05865ad2f1de722a7c6b21a0138aada9fbaeddeef7 + languageName: node + linkType: hard + +"diff-sequences@npm:^29.4.3": + version: 29.4.3 + resolution: "diff-sequences@npm:29.4.3" + checksum: 28b265e04fdddcf7f9f814effe102cc95a9dec0564a579b5aed140edb24fc345c611ca52d76d725a3cab55d3888b915b5e8a4702e0f6058968a90fa5f41fcde7 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -1044,13 +1462,13 @@ __metadata: linkType: hard "discord-api-types@npm:^0.37.41": - version: 0.37.41 - resolution: "discord-api-types@npm:0.37.41" - checksum: e8a2dd28afefe30e20652aa8eac3e15b596e7274740ea54088b3713c2a22f9bc883138d8501fa4defe3c45e895aa87fc580b0ef110a9c9ab8609c67e870948ed + version: 0.37.47 + resolution: "discord-api-types@npm:0.37.47" + checksum: 4bce0e03bcf73e16ecfd09813cda8b45a3cdefb1e89c3646b1988b54a3060daa0471ca9f3ff034e6b2d3306593fb938add8152831483e434b59980d7989d3af8 languageName: node linkType: hard -"discord.js@npm:^14.9.0": +"discord.js@npm:14.11.0": version: 14.11.0 resolution: "discord.js@npm:14.11.0" dependencies: @@ -1081,6 +1499,13 @@ __metadata: languageName: node linkType: hard +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 7d00d7cd8e49b9afa762a813faac332dee781932d6f2c848dc348939c4253f1d4564341b7af1d041853bc3f32c2ef141b58e0a4d9862c17a7f08f68df1e0f1ed + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -1088,6 +1513,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 8487182da74aabd810ac6d6f1994111dfc0e331b01271ae01ec1eb0ad7b5ecc2bbbbd2f053c05cb55a1ac30449527d819bbfbf0e3de1023db308cbcb47f86601 + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -1097,6 +1529,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:5.15.0, enhanced-resolve@npm:^5.7.0": + version: 5.15.0 + resolution: "enhanced-resolve@npm:5.15.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: fbd8cdc9263be71cc737aa8a7d6c57b43d6aa38f6cc75dde6fcd3598a130cc465f979d2f4d01bb3bf475acb43817749c79f8eef9be048683602ca91ab52e4f11 + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -1111,41 +1553,32 @@ __metadata: languageName: node linkType: hard -"esbuild-ifdef@npm:^0.2.0": - version: 0.2.0 - resolution: "esbuild-ifdef@npm:0.2.0" - peerDependencies: - esbuild: ^0.15.2 - checksum: b7702bab0ec56b9b0055abdc908b82cc278119475864341abcb9637e1ca51ced63b4308168e76b932ca1983ea9edad5259130310281c3c0b111d91cd4a9bf9e1 - languageName: node - linkType: hard - "esbuild@npm:^0.17.0, esbuild@npm:^0.17.6": - version: 0.17.18 - resolution: "esbuild@npm:0.17.18" + version: 0.17.19 + resolution: "esbuild@npm:0.17.19" dependencies: - "@esbuild/android-arm": 0.17.18 - "@esbuild/android-arm64": 0.17.18 - "@esbuild/android-x64": 0.17.18 - "@esbuild/darwin-arm64": 0.17.18 - "@esbuild/darwin-x64": 0.17.18 - "@esbuild/freebsd-arm64": 0.17.18 - "@esbuild/freebsd-x64": 0.17.18 - "@esbuild/linux-arm": 0.17.18 - "@esbuild/linux-arm64": 0.17.18 - "@esbuild/linux-ia32": 0.17.18 - "@esbuild/linux-loong64": 0.17.18 - "@esbuild/linux-mips64el": 0.17.18 - "@esbuild/linux-ppc64": 0.17.18 - "@esbuild/linux-riscv64": 0.17.18 - "@esbuild/linux-s390x": 0.17.18 - "@esbuild/linux-x64": 0.17.18 - "@esbuild/netbsd-x64": 0.17.18 - "@esbuild/openbsd-x64": 0.17.18 - "@esbuild/sunos-x64": 0.17.18 - "@esbuild/win32-arm64": 0.17.18 - "@esbuild/win32-ia32": 0.17.18 - "@esbuild/win32-x64": 0.17.18 + "@esbuild/android-arm": 0.17.19 + "@esbuild/android-arm64": 0.17.19 + "@esbuild/android-x64": 0.17.19 + "@esbuild/darwin-arm64": 0.17.19 + "@esbuild/darwin-x64": 0.17.19 + "@esbuild/freebsd-arm64": 0.17.19 + "@esbuild/freebsd-x64": 0.17.19 + "@esbuild/linux-arm": 0.17.19 + "@esbuild/linux-arm64": 0.17.19 + "@esbuild/linux-ia32": 0.17.19 + "@esbuild/linux-loong64": 0.17.19 + "@esbuild/linux-mips64el": 0.17.19 + "@esbuild/linux-ppc64": 0.17.19 + "@esbuild/linux-riscv64": 0.17.19 + "@esbuild/linux-s390x": 0.17.19 + "@esbuild/linux-x64": 0.17.19 + "@esbuild/netbsd-x64": 0.17.19 + "@esbuild/openbsd-x64": 0.17.19 + "@esbuild/sunos-x64": 0.17.19 + "@esbuild/win32-arm64": 0.17.19 + "@esbuild/win32-ia32": 0.17.19 + "@esbuild/win32-x64": 0.17.19 dependenciesMeta: "@esbuild/android-arm": optional: true @@ -1193,7 +1626,84 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 900b333f649fd89804216fb61fb5a0ffadc6dc37a2ec3b5981b588f72821676ea649a7c0ec785f0dbe6e774080b084c8af5f6ee7adbc1b138faf2a8c35e2c69c + checksum: ac11b1a5a6008e4e37ccffbd6c2c054746fc58d0ed4a2f9ee643bd030cfcea9a33a235087bc777def8420f2eaafb3486e76adb7bdb7241a9143b43a69a10afd8 + languageName: node + linkType: hard + +"esbuild@npm:^0.18.10": + version: 0.18.11 + resolution: "esbuild@npm:0.18.11" + dependencies: + "@esbuild/android-arm": 0.18.11 + "@esbuild/android-arm64": 0.18.11 + "@esbuild/android-x64": 0.18.11 + "@esbuild/darwin-arm64": 0.18.11 + "@esbuild/darwin-x64": 0.18.11 + "@esbuild/freebsd-arm64": 0.18.11 + "@esbuild/freebsd-x64": 0.18.11 + "@esbuild/linux-arm": 0.18.11 + "@esbuild/linux-arm64": 0.18.11 + "@esbuild/linux-ia32": 0.18.11 + "@esbuild/linux-loong64": 0.18.11 + "@esbuild/linux-mips64el": 0.18.11 + "@esbuild/linux-ppc64": 0.18.11 + "@esbuild/linux-riscv64": 0.18.11 + "@esbuild/linux-s390x": 0.18.11 + "@esbuild/linux-x64": 0.18.11 + "@esbuild/netbsd-x64": 0.18.11 + "@esbuild/openbsd-x64": 0.18.11 + "@esbuild/sunos-x64": 0.18.11 + "@esbuild/win32-arm64": 0.18.11 + "@esbuild/win32-ia32": 0.18.11 + "@esbuild/win32-x64": 0.18.11 + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: fbd981388fe391c4f0a1b71120ca86ee75cc6de88c01bd6883f26d8450cb3beeaae602459f9a8b9dc9e026ad68b67cda2cad5f5327c9960a53fa0cb358c61d97 languageName: node linkType: hard @@ -1204,6 +1714,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e + languageName: node + linkType: hard + "eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -1224,10 +1741,10 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.0": - version: 3.4.0 - resolution: "eslint-visitor-keys@npm:3.4.0" - checksum: 33159169462d3989321a1ec1e9aaaf6a24cc403d5d347e9886d1b5bfe18ffa1be73bdc6203143a28a606b142b1af49787f33cff0d6d0813eb5f2e8d2e1a6043c +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.0, eslint-visitor-keys@npm:^3.4.1": + version: 3.4.1 + resolution: "eslint-visitor-keys@npm:3.4.1" + checksum: f05121d868202736b97de7d750847a328fcfa8593b031c95ea89425333db59676ac087fa905eba438d0a3c5769632f828187e0c1a0d271832a2153c1d3661c2c languageName: node linkType: hard @@ -1281,14 +1798,14 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.5.1": - version: 9.5.1 - resolution: "espree@npm:9.5.1" +"espree@npm:^9.5.1, espree@npm:^9.6.0": + version: 9.6.0 + resolution: "espree@npm:9.6.0" dependencies: - acorn: ^8.8.0 + acorn: ^8.9.0 acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.4.0 - checksum: cdf6e43540433d917c4f2ee087c6e987b2063baa85a1d9cdaf51533d78275ebd5910c42154e7baf8e3e89804b386da0a2f7fad2264d8f04420e7506bf87b3b88 + eslint-visitor-keys: ^3.4.1 + checksum: 1287979510efb052a6a97c73067ea5d0a40701b29adde87bbe2d3eb1667e39ca55e8129e20e2517fed3da570150e7ef470585228459a8f3e3755f45007a1c662 languageName: node linkType: hard @@ -1348,6 +1865,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -1356,15 +1880,15 @@ __metadata: linkType: hard "fast-glob@npm:^3.2.9": - version: 3.2.12 - resolution: "fast-glob@npm:3.2.12" + version: 3.3.0 + resolution: "fast-glob@npm:3.3.0" dependencies: "@nodelib/fs.stat": ^2.0.2 "@nodelib/fs.walk": ^1.2.3 glob-parent: ^5.1.2 merge2: ^1.3.0 micromatch: ^4.0.4 - checksum: 0b1990f6ce831c7e28c4d505edcdaad8e27e88ab9fa65eedadb730438cfc7cde4910d6c975d6b7b8dc8a73da4773702ebcfcd6e3518e73938bb1383badfe01c2 + checksum: 20df62be28eb5426fe8e40e0d05601a63b1daceb7c3d87534afcad91bdcf1e4b1743cf2d5247d6e225b120b46df0b9053a032b2691ba34ee121e033acd81f547 languageName: node linkType: hard @@ -1391,6 +1915,16 @@ __metadata: languageName: node linkType: hard +"figures@npm:5.0.0": + version: 5.0.0 + resolution: "figures@npm:5.0.0" + dependencies: + escape-string-regexp: ^5.0.0 + is-unicode-supported: ^1.2.0 + checksum: e6e8b6d1df2f554d4effae4a5ceff5d796f9449f6d4e912d74dab7d5f25916ecda6c305b9084833157d56485a0c78b37164430ddc5675bcee1330e346710669e + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -1401,13 +1935,13 @@ __metadata: linkType: hard "file-type@npm:^18.3.0": - version: 18.3.0 - resolution: "file-type@npm:18.3.0" + version: 18.5.0 + resolution: "file-type@npm:18.5.0" dependencies: readable-web-to-node-stream: ^3.0.2 strtok3: ^7.0.0 token-types: ^5.0.1 - checksum: 7b2a08cc1f8737b65837a83b8077ba4ea624e5de82fa866814e634f03e19695fefd755fac440a6eb81a273601a342b05cee1f70f2bc032be8424d7c7d135eb51 + checksum: d2bc81d842b110970a0ca9d90356ce4e9738c1c05596ce8931f2af334477856d92bcecd0742dc6646e13a970c0125150ad4415898688d1901d80e972d90ab1ca languageName: node linkType: hard @@ -1447,7 +1981,17 @@ __metadata: languageName: node linkType: hard -"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": +"foreground-child@npm:^3.1.0": + version: 3.1.1 + resolution: "foreground-child@npm:3.1.1" + dependencies: + cross-spawn: ^7.0.0 + signal-exit: ^4.0.1 + checksum: 139d270bc82dc9e6f8bc045fe2aae4001dc2472157044fdfad376d0a3457f77857fa883c1c8b21b491c6caade9a926a4bed3d3d2e8d3c9202b151a4cbbd0bcd5 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" dependencies: @@ -1456,6 +2000,15 @@ __metadata: languageName: node linkType: hard +"fs-minipass@npm:^3.0.0": + version: 3.0.2 + resolution: "fs-minipass@npm:3.0.2" + dependencies: + minipass: ^5.0.0 + checksum: e9cc0e1f2d01c6f6f62f567aee59530aba65c6c7b2ae88c5027bc34c711ebcfcfaefd0caf254afa6adfe7d1fba16bc2537508a6235196bac7276747d078aef0a + languageName: node + linkType: hard + "fs.realpath@npm:^1.0.0": version: 1.0.0 resolution: "fs.realpath@npm:1.0.0" @@ -1482,6 +2035,13 @@ __metadata: languageName: node linkType: hard +"function-bind@npm:^1.1.1": + version: 1.1.1 + resolution: "function-bind@npm:1.1.1" + checksum: b32fbaebb3f8ec4969f033073b43f5c8befbb58f1a79e12f1d7490358150359ebd92f49e72ff0144f65f2c48ea2a605bff2d07965f548f6474fd8efd95bf361a + languageName: node + linkType: hard + "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -1498,6 +2058,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.0": + version: 2.0.0 + resolution: "get-func-name@npm:2.0.0" + checksum: 8d82e69f3e7fab9e27c547945dfe5cc0c57fc0adf08ce135dddb01081d75684a03e7a0487466f478872b341d52ac763ae49e660d01ab83741f74932085f693c3 + languageName: node + linkType: hard + "get-stream@npm:^6.0.0": version: 6.0.1 resolution: "get-stream@npm:6.0.1" @@ -1523,6 +2090,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:10.3.1": + version: 10.3.1 + resolution: "glob@npm:10.3.1" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 + path-scurry: ^1.10.0 + bin: + glob: dist/cjs/src/bin.js + checksum: 19c8c2805658b1002fecf0722cd609a33153d756a0d5260676bd0e9c5e6ef889ec9cce6d3dac0411aa90bce8de3d14f25b6f5589a3292582cccbfeddd0e98cc4 + languageName: node + linkType: hard + "glob@npm:7.1.6": version: 7.1.6 resolution: "glob@npm:7.1.6" @@ -1537,6 +2119,21 @@ __metadata: languageName: node linkType: hard +"glob@npm:^10.2.2": + version: 10.3.3 + resolution: "glob@npm:10.3.3" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^2.0.3 + minimatch: ^9.0.1 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + path-scurry: ^1.10.1 + bin: + glob: dist/cjs/src/bin.js + checksum: 29190d3291f422da0cb40b77a72fc8d2c51a36524e99b8bf412548b7676a6627489528b57250429612b6eec2e6fe7826d328451d3e694a9d15e575389308ec53 + languageName: node + linkType: hard + "glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -1551,16 +2148,12 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1": - version: 8.1.0 - resolution: "glob@npm:8.1.0" +"global-dirs@npm:^3.0.0": + version: 3.0.1 + resolution: "global-dirs@npm:3.0.1" dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 + ini: 2.0.0 + checksum: 70147b80261601fd40ac02a104581432325c1c47329706acd773f3a6ce99bb36d1d996038c85ccacd482ad22258ec233c586b6a91535b1a116b89663d49d6438 languageName: node linkType: hard @@ -1587,7 +2180,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -1601,6 +2194,24 @@ __metadata: languageName: node linkType: hard +"handlebars@npm:4.7.7": + version: 4.7.7 + resolution: "handlebars@npm:4.7.7" + dependencies: + minimist: ^1.2.5 + neo-async: ^2.6.0 + source-map: ^0.6.1 + uglify-js: ^3.1.4 + wordwrap: ^1.0.0 + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 1e79a43f5e18d15742977cb987923eab3e2a8f44f2d9d340982bcb69e1735ed049226e534d7c1074eaddaf37e4fb4f471a8adb71cddd5bc8cf3f894241df5cee + languageName: node + linkType: hard + "has-flag@npm:^4.0.0": version: 4.0.0 resolution: "has-flag@npm:4.0.0" @@ -1615,7 +2226,16 @@ __metadata: languageName: node linkType: hard -"http-cache-semantics@npm:^4.1.0": +"has@npm:^1.0.3": + version: 1.0.3 + resolution: "has@npm:1.0.3" + dependencies: + function-bind: ^1.1.1 + checksum: b9ad53d53be4af90ce5d1c38331e712522417d017d5ef1ebd0507e07c2fbad8686fffb8e12ddecd4c39ca9b9b47431afbb975b8abf7f3c3b82c98e9aad052792 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 @@ -1675,7 +2295,7 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.2.0": +"ignore@npm:5.2.4, ignore@npm:^5.2.0": version: 5.2.4 resolution: "ignore@npm:5.2.4" checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef @@ -1699,6 +2319,13 @@ __metadata: languageName: node linkType: hard +"indent-string@npm:5.0.0": + version: 5.0.0 + resolution: "indent-string@npm:5.0.0" + checksum: e466c27b6373440e6d84fbc19e750219ce25865cb82d578e41a6053d727e5520dc5725217d6eb1cc76005a1bb1696a0f106d84ce7ebda3033b963a38583fb3b3 + languageName: node + linkType: hard + "indent-string@npm:^4.0.0": version: 4.0.0 resolution: "indent-string@npm:4.0.0" @@ -1706,13 +2333,6 @@ __metadata: languageName: node linkType: hard -"infer-owner@npm:^1.0.4": - version: 1.0.4 - resolution: "infer-owner@npm:1.0.4" - checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 - languageName: node - linkType: hard - "inflight@npm:^1.0.4": version: 1.0.6 resolution: "inflight@npm:1.0.6" @@ -1730,6 +2350,20 @@ __metadata: languageName: node linkType: hard +"ini@npm:2.0.0": + version: 2.0.0 + resolution: "ini@npm:2.0.0" + checksum: e7aadc5fb2e4aefc666d74ee2160c073995a4061556b1b5b4241ecb19ad609243b9cceafe91bae49c219519394bbd31512516cb22a3b1ca6e66d869e0447e84e + languageName: node + linkType: hard + +"interpret@npm:^3.1.1": + version: 3.1.1 + resolution: "interpret@npm:3.1.1" + checksum: 35cebcf48c7351130437596d9ab8c8fe131ce4038da4561e6d665f25640e0034702a031cf7e3a5cea60ac7ac548bf17465e0571ede126f3d3a6933152171ac82 + languageName: node + linkType: hard + "ip@npm:^2.0.0": version: 2.0.0 resolution: "ip@npm:2.0.0" @@ -1746,6 +2380,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.12.0": + version: 2.12.1 + resolution: "is-core-module@npm:2.12.1" + dependencies: + has: ^1.0.3 + checksum: f04ea30533b5e62764e7b2e049d3157dc0abd95ef44275b32489ea2081176ac9746ffb1cdb107445cf1ff0e0dfcad522726ca27c27ece64dadf3795428b8e468 + languageName: node + linkType: hard + "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -1769,6 +2412,16 @@ __metadata: languageName: node linkType: hard +"is-installed-globally@npm:0.4.0": + version: 0.4.0 + resolution: "is-installed-globally@npm:0.4.0" + dependencies: + global-dirs: ^3.0.0 + is-path-inside: ^3.0.2 + checksum: 3359840d5982d22e9b350034237b2cda2a12bac1b48a721912e1ab8e0631dd07d45a2797a120b7b87552759a65ba03e819f1bd63f2d7ab8657ec0b44ee0bf399 + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -1783,7 +2436,7 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.3": +"is-path-inside@npm:^3.0.2, is-path-inside@npm:^3.0.3": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" checksum: abd50f06186a052b349c15e55b182326f1936c89a78bf6c8f2b707412517c097ce04bc49a0ca221787bc44e1049f51f09a2ffb63d22899051988d3a618ba13e9 @@ -1797,6 +2450,13 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^1.2.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 20a1fc161afafaf49243551a5ac33b6c4cf0bbcce369fcd8f2951fbdd000c30698ce320de3ee6830497310a8f41880f8066d440aa3eb0a853e2aa4836dd89abc + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -1813,6 +2473,19 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^2.0.3": + version: 2.2.1 + resolution: "jackspeak@npm:2.2.1" + dependencies: + "@isaacs/cliui": ^8.0.2 + "@pkgjs/parseargs": ^0.11.0 + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: e29291c0d0f280a063fa18fbd1e891ab8c2d7519fd34052c0ebde38538a15c603140d60c2c7f432375ff7ee4c5f1c10daa8b2ae19a97c3d4affe308c8360c1df + languageName: node + linkType: hard + "joycon@npm:^3.0.1": version: 3.1.1 resolution: "joycon@npm:3.1.1" @@ -1821,9 +2494,9 @@ __metadata: linkType: hard "js-sdsl@npm:^4.1.4": - version: 4.4.0 - resolution: "js-sdsl@npm:4.4.0" - checksum: 7bb08a2d746ab7ff742720339aa006c631afe05e77d11eda988c1c35fae8e03e492e4e347e883e786e3ce6170685d4780c125619111f0730c11fdb41b04059c7 + version: 4.4.1 + resolution: "js-sdsl@npm:4.4.1" + checksum: ba445b53531f2f353f8f66ed8c7edc7942c9bac68707161aa70528fa8ee9a89805d170cff171aa40bdac1aed5dfe97dce6f929e6f759a487ed619387a5ea1365 languageName: node linkType: hard @@ -1845,6 +2518,13 @@ __metadata: languageName: node linkType: hard +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -1852,6 +2532,29 @@ __metadata: languageName: node linkType: hard +"json5@npm:2.2.3, json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 + languageName: node + linkType: hard + +"jsonc-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -1883,6 +2586,13 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^0.4.3": + version: 0.4.3 + resolution: "local-pkg@npm:0.4.3" + checksum: 7825aca531dd6afa3a3712a0208697aa4a5cd009065f32e3fb732aafcc42ed11f277b5ac67229222e96f4def55197171cdf3d5522d0381b489d2e5547b407d55 + languageName: node + linkType: hard + "locate-path@npm:^6.0.0": version: 6.0.0 resolution: "locate-path@npm:6.0.0" @@ -1913,13 +2623,22 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.21": +"lodash@npm:4.17.21, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 languageName: node linkType: hard +"loupe@npm:^2.3.1, loupe@npm:^2.3.6": + version: 2.3.6 + resolution: "loupe@npm:2.3.6" + dependencies: + get-func-name: ^2.0.0 + checksum: cc83f1b124a1df7384601d72d8d1f5fe95fd7a8185469fec48bb2e4027e45243949e7a013e8d91051a138451ff0552310c32aa9786e60b6a30d1e801bdc2163f + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -1936,27 +2655,42 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": - version: 10.2.1 - resolution: "make-fetch-happen@npm:10.2.1" +"lru-cache@npm:^9.1.1 || ^10.0.0": + version: 10.0.0 + resolution: "lru-cache@npm:10.0.0" + checksum: 18f101675fe283bc09cda0ef1e3cc83781aeb8373b439f086f758d1d91b28730950db785999cd060d3c825a8571c03073e8c14512b6655af2188d623031baf50 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.1": + version: 0.30.1 + resolution: "magic-string@npm:0.30.1" + dependencies: + "@jridgewell/sourcemap-codec": ^1.4.15 + checksum: 7bc7e4493e32a77068f3753bf8652d4ab44142122eb7fb9fa871af83bef2cd2c57518a6769701cd5d0379bd624a13bc8c72ca25ac5655b27e5a61adf1fd38db2 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^11.0.3": + version: 11.1.1 + resolution: "make-fetch-happen@npm:11.1.1" dependencies: agentkeepalive: ^4.2.1 - cacache: ^16.1.0 - http-cache-semantics: ^4.1.0 + cacache: ^17.0.0 + http-cache-semantics: ^4.1.1 http-proxy-agent: ^5.0.0 https-proxy-agent: ^5.0.0 is-lambda: ^1.0.1 lru-cache: ^7.7.1 - minipass: ^3.1.6 - minipass-collect: ^1.0.2 - minipass-fetch: ^2.0.3 + minipass: ^5.0.0 + minipass-fetch: ^3.0.0 minipass-flush: ^1.0.5 minipass-pipeline: ^1.2.4 negotiator: ^0.6.3 promise-retry: ^2.0.1 socks-proxy-agent: ^7.0.0 - ssri: ^9.0.0 - checksum: 2332eb9a8ec96f1ffeeea56ccefabcb4193693597b132cd110734d50f2928842e22b84cfa1508e921b8385cdfd06dda9ad68645fed62b50fff629a580f5fb72c + ssri: ^10.0.0 + checksum: 7268bf274a0f6dcf0343829489a4506603ff34bd0649c12058753900b0eb29191dce5dba12680719a5d0a983d3e57810f594a12f3c18494e93a1fbc6348a4540 languageName: node linkType: hard @@ -2000,12 +2734,19 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" +"minimatch@npm:^9.0.1": + version: 9.0.3 + resolution: "minimatch@npm:9.0.3" dependencies: brace-expansion: ^2.0.1 - checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77 + checksum: 253487976bf485b612f16bf57463520a14f512662e592e95c571afdab1442a6a6864b6c88f248ce6fc4ff0b6de04ac7aa6c8bb51e868e99d1d65eb0658a708b5 + languageName: node + linkType: hard + +"minimist@npm:^1.2.5, minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0 languageName: node linkType: hard @@ -2018,18 +2759,18 @@ __metadata: languageName: node linkType: hard -"minipass-fetch@npm:^2.0.3": - version: 2.1.2 - resolution: "minipass-fetch@npm:2.1.2" +"minipass-fetch@npm:^3.0.0": + version: 3.0.3 + resolution: "minipass-fetch@npm:3.0.3" dependencies: encoding: ^0.1.13 - minipass: ^3.1.6 + minipass: ^5.0.0 minipass-sized: ^1.0.3 minizlib: ^2.1.2 dependenciesMeta: encoding: optional: true - checksum: 3f216be79164e915fc91210cea1850e488793c740534985da017a4cbc7a5ff50506956d0f73bb0cb60e4fe91be08b6b61ef35101706d3ef5da2c8709b5f08f91 + checksum: af5ab2552a16fcf505d35fd7ffb84b57f4a0eeb269e6e1d9a2a75824dda48b36e527083250b7cca4a4def21d9544e2ade441e4730e233c0bc2133f6abda31e18 languageName: node linkType: hard @@ -2060,7 +2801,7 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": +"minipass@npm:^3.0.0": version: 3.3.6 resolution: "minipass@npm:3.3.6" dependencies: @@ -2069,10 +2810,24 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.0.0": - version: 4.2.8 - resolution: "minipass@npm:4.2.8" - checksum: 7f4914d5295a9a30807cae5227a37a926e6d910c03f315930fde52332cf0575dfbc20295318f91f0baf0e6bb11a6f668e30cde8027dea7a11b9d159867a3c830 +"minipass@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass@npm:5.0.0" + checksum: 425dab288738853fded43da3314a0b5c035844d6f3097a8e3b5b29b328da8f3c1af6fc70618b32c29ff906284cf6406b6841376f21caaadd0793c1d5a6a620ea + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2": + version: 6.0.2 + resolution: "minipass@npm:6.0.2" + checksum: d140b91f4ab2e5ce5a9b6c468c0e82223504acc89114c1a120d4495188b81fedf8cade72a9f4793642b4e66672f990f1e0d902dd858485216a07cd3c8a62fac9 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": + version: 7.0.1 + resolution: "minipass@npm:7.0.1" + checksum: fedd1293f6a1b4e406c242a1cecfb75d0a81422bb2c365d999e33a88642fb68d70a89d95b550e08c640b3c0d9162829310e0c58b9b846b9218de25779818c709 languageName: node linkType: hard @@ -2086,7 +2841,7 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": +"mkdirp@npm:^1.0.3": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" bin: @@ -2095,6 +2850,18 @@ __metadata: languageName: node linkType: hard +"mlly@npm:^1.2.0, mlly@npm:^1.4.0": + version: 1.4.0 + resolution: "mlly@npm:1.4.0" + dependencies: + acorn: ^8.9.0 + pathe: ^1.1.1 + pkg-types: ^1.0.3 + ufo: ^1.1.2 + checksum: ebf2e2b5cfb4c6e45e8d0bbe82710952247023f12626cb0997c41b1bb6e57c8b6fc113aa709228ad511382ab0b4eebaab759806be0578093b3635d3e940bd63b + languageName: node + linkType: hard + "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -2120,6 +2887,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.6": + version: 3.3.6 + resolution: "nanoid@npm:3.3.6" + bin: + nanoid: bin/nanoid.cjs + checksum: 7d0eda657002738aa5206107bd0580aead6c95c460ef1bdd0b1a87a9c7ae6277ac2e9b945306aaa5b32c6dcb7feaf462d0f552e7f8b5718abfc6ead5c94a71b3 + languageName: node + linkType: hard + "natural-compare-lite@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare-lite@npm:1.4.0" @@ -2141,14 +2917,22 @@ __metadata: languageName: node linkType: hard +"neo-async@npm:^2.6.0": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9 + languageName: node + linkType: hard + "node-gyp@npm:latest": - version: 9.3.1 - resolution: "node-gyp@npm:9.3.1" + version: 9.4.0 + resolution: "node-gyp@npm:9.4.0" dependencies: env-paths: ^2.2.0 + exponential-backoff: ^3.1.1 glob: ^7.1.4 graceful-fs: ^4.2.6 - make-fetch-happen: ^10.0.3 + make-fetch-happen: ^11.0.3 nopt: ^6.0.0 npmlog: ^6.0.0 rimraf: ^3.0.2 @@ -2157,7 +2941,7 @@ __metadata: which: ^2.0.2 bin: node-gyp: bin/node-gyp.js - checksum: b860e9976fa645ca0789c69e25387401b4396b93c8375489b5151a6c55cf2640a3b6183c212b38625ef7c508994930b72198338e3d09b9d7ade5acc4aaf51ea7 + checksum: 78b404e2e0639d64e145845f7f5a3cb20c0520cdaf6dda2f6e025e9b644077202ea7de1232396ba5bde3fee84cdc79604feebe6ba3ec84d464c85d407bb5da99 languageName: node linkType: hard @@ -2226,16 +3010,16 @@ __metadata: linkType: hard "optionator@npm:^0.9.1": - version: 0.9.1 - resolution: "optionator@npm:0.9.1" + version: 0.9.3 + resolution: "optionator@npm:0.9.3" dependencies: + "@aashutoshrathi/word-wrap": ^1.2.3 deep-is: ^0.1.3 fast-levenshtein: ^2.0.6 levn: ^0.4.1 prelude-ls: ^1.2.1 type-check: ^0.4.0 - word-wrap: ^1.2.3 - checksum: dbc6fa065604b24ea57d734261914e697bd73b69eff7f18e967e8912aa2a40a19a9f599a507fa805be6c13c24c4eae8c71306c239d517d42d4c041c942f508a0 + checksum: 09281999441f2fe9c33a5eeab76700795365a061563d66b098923eb719251a42bdbe432790d35064d0816ead9296dbeb1ad51a733edf4167c96bd5d0882e428a languageName: node linkType: hard @@ -2248,6 +3032,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: ^1.0.0 + checksum: 01d9d70695187788f984226e16c903475ec6a947ee7b21948d6f597bed788e3112cc7ec2e171c1d37125057a5f45f3da21d8653e04a3a793589e12e9e80e756b + languageName: node + linkType: hard + "p-locate@npm:^5.0.0": version: 5.0.0 resolution: "p-locate@npm:5.0.0" @@ -2296,6 +3089,23 @@ __metadata: languageName: node linkType: hard +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a + languageName: node + linkType: hard + +"path-scurry@npm:^1.10.0, path-scurry@npm:^1.10.1": + version: 1.10.1 + resolution: "path-scurry@npm:1.10.1" + dependencies: + lru-cache: ^9.1.1 || ^10.0.0 + minipass: ^5.0.0 || ^6.0.2 || ^7.0.0 + checksum: e2557cff3a8fb8bc07afdd6ab163a92587884f9969b05bbbaf6fe7379348bfb09af9ed292af12ed32398b15fb443e81692047b786d1eeb6d898a51eb17ed7d90 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -2303,6 +3113,20 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^1.1.0, pathe@npm:^1.1.1": + version: 1.1.1 + resolution: "pathe@npm:1.1.1" + checksum: 34ab3da2e5aa832ebc6a330ffe3f73d7ba8aec6e899b53b8ec4f4018de08e40742802deb12cf5add9c73b7bf719b62c0778246bd376ca62b0fb23e0dde44b759 + languageName: node + linkType: hard + +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 090e3147716647fb7fb5b4b8c8e5b55e5d0a6086d085b6cd23f3d3c01fcf0ff56fd3cc22f2f4a033bd2e46ed55d61ed8379e123b42afe7d531a2a5fc8bb556d6 + languageName: node + linkType: hard + "peek-readable@npm:^5.0.0": version: 5.0.0 resolution: "peek-readable@npm:5.0.0" @@ -2310,6 +3134,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.0.0": + version: 1.0.0 + resolution: "picocolors@npm:1.0.0" + checksum: a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -2318,9 +3149,20 @@ __metadata: linkType: hard "pirates@npm:^4.0.1": - version: 4.0.5 - resolution: "pirates@npm:4.0.5" - checksum: c9994e61b85260bec6c4fc0307016340d9b0c4f4b6550a957afaaff0c9b1ad58fbbea5cfcf083860a25cb27a375442e2b0edf52e2e1e40e69934e08dcc52d227 + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6 + languageName: node + linkType: hard + +"pkg-types@npm:^1.0.3": + version: 1.0.3 + resolution: "pkg-types@npm:1.0.3" + dependencies: + jsonc-parser: ^3.2.0 + mlly: ^1.2.0 + pathe: ^1.1.0 + checksum: 4b305c834b912ddcc8a0fe77530c0b0321fe340396f84cbb87aecdbc126606f47f2178f23b8639e71a4870f9631c7217aef52ffed0ae17ea2dbbe7e43d116a6e languageName: node linkType: hard @@ -2342,6 +3184,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.24": + version: 8.4.25 + resolution: "postcss@npm:8.4.25" + dependencies: + nanoid: ^3.3.6 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 9ed3ab8af43ad5210c28f56f916fd9b8c9f94fbeaebbf645dcf579bc28bdd8056c2a7ecc934668d399b81fedb6128f0c4b299f931e50454964bc911c25a3a0a2 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -2358,10 +3211,14 @@ __metadata: languageName: node linkType: hard -"promise-inflight@npm:^1.0.1": - version: 1.0.1 - resolution: "promise-inflight@npm:1.0.1" - checksum: 22749483091d2c594261517f4f80e05226d4d5ecc1fc917e1886929da56e22b5718b7f2a75f3807e7a7d471bc3be2907fe92e6e8f373ddf5c64bae35b5af3981 +"pretty-format@npm:^29.5.0": + version: 29.6.1 + resolution: "pretty-format@npm:29.6.1" + dependencies: + "@jest/schemas": ^29.6.0 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 6f923a2379a37a425241dc223d76f671c73c4f37dba158050575a54095867d565c068b441843afdf3d7c37bed9df4bbadf46297976e60d4149972b779474203a languageName: node linkType: hard @@ -2375,6 +3232,16 @@ __metadata: languageName: node linkType: hard +"prompts@npm:2.4.2": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: ^3.0.3 + sisteransi: ^1.0.5 + checksum: d8fd1fe63820be2412c13bfc5d0a01909acc1f0367e32396962e737cb2fc52d004f3302475d5ce7d18a1e8a79985f93ff04ee03007d091029c3f9104bffc007d + languageName: node + linkType: hard + "punycode@npm:^2.1.0": version: 2.3.0 resolution: "punycode@npm:2.3.0" @@ -2389,6 +3256,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" @@ -2418,6 +3292,31 @@ __metadata: languageName: node linkType: hard +"rechoir@npm:^0.8.0": + version: 0.8.0 + resolution: "rechoir@npm:0.8.0" + dependencies: + resolve: ^1.20.0 + checksum: ad3caed8afdefbc33fbc30e6d22b86c35b3d51c2005546f4e79bcc03c074df804b3640ad18945e6bef9ed12caedc035655ec1082f64a5e94c849ff939dc0a788 + languageName: node + linkType: hard + +"regexp-tree@npm:~0.1.1": + version: 0.1.27 + resolution: "regexp-tree@npm:0.1.27" + bin: + regexp-tree: bin/regexp-tree + checksum: 129aebb34dae22d6694ab2ac328be3f99105143737528ab072ef624d599afecbcfae1f5c96a166fa9e5f64fa1ecf30b411c4691e7924c3e11bbaf1712c260c54 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: a03ef6895445f33a4015300c426699bc66b2b044ba7b670aa238610381b56d3f07c686251740d575e22f4c87531ba662d06937508f0f3c0f1ddc04db3130560b + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -2432,6 +3331,32 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.20.0": + version: 1.22.3 + resolution: "resolve@npm:1.22.3" + dependencies: + is-core-module: ^2.12.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: fb834b81348428cb545ff1b828a72ea28feb5a97c026a1cf40aa1008352c72811ff4d4e71f2035273dc536dcfcae20c13604ba6283c612d70fa0b6e44519c374 + languageName: node + linkType: hard + +"resolve@patch:resolve@^1.20.0#~builtin": + version: 1.22.3 + resolution: "resolve@patch:resolve@npm%3A1.22.3#~builtin::version=1.22.3&hash=c3c19d" + dependencies: + is-core-module: ^2.12.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: ad59734723b596d0891321c951592ed9015a77ce84907f89c9d9307dd0c06e11a67906a3e628c4cae143d3e44898603478af0ddeb2bba3f229a9373efe342665 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -2457,9 +3382,9 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^3.2.5": - version: 3.21.0 - resolution: "rollup@npm:3.21.0" +"rollup@npm:^3.2.5, rollup@npm:^3.25.2": + version: 3.26.2 + resolution: "rollup@npm:3.26.2" dependencies: fsevents: ~2.3.2 dependenciesMeta: @@ -2467,7 +3392,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: f3294d712147c0975c59ff81b3010dc08d07743cdad72fbe12879044b3e467139b3c2aeec85768656c4f7ec6a7b3d19354a78fc2050044bf8e90a499e145e31e + checksum: 7186beeba0e6fd33b37aa33ede7157e77af0a0be4b75d345a51dab4be5962a8e7a1c5ec8868e7c4cd361b456eb1b47bdb75e5db32ab37a546bed383d06b40384 languageName: node linkType: hard @@ -2496,6 +3421,15 @@ __metadata: languageName: node linkType: hard +"safe-regex@npm:2.1.1": + version: 2.1.1 + resolution: "safe-regex@npm:2.1.1" + dependencies: + regexp-tree: ~0.1.1 + checksum: 5d734e2193c63ef0cb00f60c0244e0f8a30ecb31923633cd34636808d6a7c4c206d650017953ae1db8bc33967c2f06af33488dea6f038f4e38212beb7bed77b4 + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -2503,14 +3437,23 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.5.0 - resolution: "semver@npm:7.5.0" +"semver-try-require@npm:6.2.3": + version: 6.2.3 + resolution: "semver-try-require@npm:6.2.3" + dependencies: + semver: ^7.5.3 + checksum: 891ef070f0817342f84692c2ed015f04ab49758722a51261f634d13a426bda899c89dce3b6bb7593b33c5b47283d2bf273fe0649807813fe814466e1017af523 + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.3": + version: 7.5.4 + resolution: "semver@npm:7.5.4" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 2d266937756689a76f124ffb4c1ea3e1bbb2b263219f90ada8a11aebebe1280b13bb76cca2ca96bdee3dbc554cbc0b24752eb895b2a51577aa644427e9229f2b + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 languageName: node linkType: hard @@ -2537,6 +3480,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 8aa5a98640ca09fe00d74416eca97551b3e42991614a3d1b824b115fc1401543650914f651ab1311518177e4d297e80b953f4cd4cd7ea1eabe824e8f2091de01 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -2544,6 +3494,20 @@ __metadata: languageName: node linkType: hard +"signal-exit@npm:^4.0.1": + version: 4.0.2 + resolution: "signal-exit@npm:4.0.2" + checksum: 41f5928431cc6e91087bf0343db786a6313dd7c6fd7e551dbc141c95bb5fb26663444fd9df8ea47c5d7fc202f60aa7468c3162a9365cbb0615fc5e1b1328fe31 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: aba6438f46d2bfcef94cf112c835ab395172c75f67453fe05c340c770d3c402363018ae1ab4172a1026a90c47eaccf3af7b6ff6fa749a680c2929bd7fa2b37a4 + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -2579,6 +3543,13 @@ __metadata: languageName: node linkType: hard +"source-map-js@npm:^1.0.2": + version: 1.0.2 + resolution: "source-map-js@npm:1.0.2" + checksum: c049a7fc4deb9a7e9b481ae3d424cc793cb4845daa690bc5a05d428bf41bf231ced49b4cf0c9e77f9d42fdb3d20d6187619fc586605f5eabe995a316da8d377c + languageName: node + linkType: hard + "source-map@npm:0.8.0-beta.0": version: 0.8.0-beta.0 resolution: "source-map@npm:0.8.0-beta.0" @@ -2588,12 +3559,33 @@ __metadata: languageName: node linkType: hard -"ssri@npm:^9.0.0": - version: 9.0.1 - resolution: "ssri@npm:9.0.1" +"source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 + languageName: node + linkType: hard + +"ssri@npm:^10.0.0": + version: 10.0.4 + resolution: "ssri@npm:10.0.4" dependencies: - minipass: ^3.1.1 - checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb + minipass: ^5.0.0 + checksum: fb14da9f8a72b04eab163eb13a9dda11d5962cd2317f85457c4e0b575e9a6e0e3a6a87b5bf122c75cb36565830cd5f263fb457571bf6f1587eb5f95d095d6165 + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99 + languageName: node + linkType: hard + +"std-env@npm:^3.3.3": + version: 3.3.3 + resolution: "std-env@npm:3.3.3" + checksum: 6665f6d8bd63aae432d3eb9abbd7322847ad0d902603e6dce1e8051b4f42ceeb4f7f96a4faf70bb05ce65ceee2dc982502b701575c8a58b1bfad29f3dbb19f81 languageName: node linkType: hard @@ -2604,7 +3596,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -2615,6 +3607,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^9.2.2 + strip-ansi: ^7.0.1 + checksum: 7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + "string_decoder@npm:^1.1.1": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" @@ -2624,7 +3627,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -2633,6 +3636,22 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: ^6.0.1 + checksum: 859c73fcf27869c22a4e4d8c6acfe690064659e84bef9458aa6d13719d09ca88dcfd40cbf31fd0be63518ea1a643fe070b4827d353e09533a5b0b9fd4553d64d + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b + languageName: node + linkType: hard + "strip-final-newline@npm:^2.0.0": version: 2.0.0 resolution: "strip-final-newline@npm:2.0.0" @@ -2647,6 +3666,15 @@ __metadata: languageName: node linkType: hard +"strip-literal@npm:^1.0.1": + version: 1.0.1 + resolution: "strip-literal@npm:1.0.1" + dependencies: + acorn: ^8.8.2 + checksum: ab40496820f02220390d95cdd620a997168efb69d5bd7d180bc4ef83ca562a95447843d8c7c88b8284879a29cf4eedc89d8001d1e098c1a1e23d12a9c755dff4 + languageName: node + linkType: hard + "strtok3@npm:^7.0.0": version: 7.0.0 resolution: "strtok3@npm:7.0.0" @@ -2684,17 +3712,38 @@ __metadata: languageName: node linkType: hard +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 53b1e247e68e05db7b3808b99b892bd36fb096e6fba213a06da7fab22045e97597db425c724f2bbd6c99a3c295e1e73f3e4de78592289f38431049e1277ca0ae + languageName: node + linkType: hard + +"tapable@npm:^2.2.0": + version: 2.2.1 + resolution: "tapable@npm:2.2.1" + checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51 + languageName: node + linkType: hard + "tar@npm:^6.1.11, tar@npm:^6.1.2": - version: 6.1.13 - resolution: "tar@npm:6.1.13" + version: 6.1.15 + resolution: "tar@npm:6.1.15" dependencies: chownr: ^2.0.0 fs-minipass: ^2.0.0 - minipass: ^4.0.0 + minipass: ^5.0.0 minizlib: ^2.1.1 mkdirp: ^1.0.3 yallist: ^4.0.0 - checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c + checksum: f23832fceeba7578bf31907aac744ae21e74a66f4a17a9e94507acf460e48f6db598c7023882db33bab75b80e027c21f276d405e4a0322d58f51c7088d428268 + languageName: node + linkType: hard + +"teamcity-service-messages@npm:0.1.14": + version: 0.1.14 + resolution: "teamcity-service-messages@npm:0.1.14" + checksum: b4f91d81158df1f977bdf10312779dc1bb47b3268dbdbab7cdf1e9d99591d2d4916af975fe059f62481bff1e7938dff1b46c05ba53545d53fd486de079879d9b languageName: node linkType: hard @@ -2723,6 +3772,27 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.5.0": + version: 2.5.0 + resolution: "tinybench@npm:2.5.0" + checksum: 284bb9428f197ec8b869c543181315e65e41ccfdad3c4b6c916bb1fdae1b5c6785661b0d90cf135b48d833b03cb84dc5357b2d33ec65a1f5971fae0ab2023821 + languageName: node + linkType: hard + +"tinypool@npm:^0.6.0": + version: 0.6.0 + resolution: "tinypool@npm:0.6.0" + checksum: 996bf3a922993cec568d6b6ddc72531700b2a8aea24623ed6946a8929557b0f17629955d20defda09cb3b12fc94087159f14cb8e06570adce7d1b7d2eef00a91 + languageName: node + linkType: hard + +"tinyspy@npm:^2.1.1": + version: 2.1.1 + resolution: "tinyspy@npm:2.1.1" + checksum: cfe669803a7f11ca912742b84c18dcc4ceecaa7661c69bc5eb608a8a802d541c48aba220df8929f6c8cd09892ad37cb5ba5958ddbbb57940e91d04681d3cee73 + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -2781,6 +3851,28 @@ __metadata: languageName: node linkType: hard +"tsconfig-paths-webpack-plugin@npm:4.0.1": + version: 4.0.1 + resolution: "tsconfig-paths-webpack-plugin@npm:4.0.1" + dependencies: + chalk: ^4.1.0 + enhanced-resolve: ^5.7.0 + tsconfig-paths: ^4.1.2 + checksum: a09e765c4939379fa060f78bbc906d0a7be541b9b49d3ad4744e99a2e87f28aa30d549a549196159bc5a50f420fdb1391b8eed360f47ee8ce40e15dcb800b5aa + languageName: node + linkType: hard + +"tsconfig-paths@npm:^4.1.2": + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" + dependencies: + json5: ^2.2.2 + minimist: ^1.2.6 + strip-bom: ^3.0.0 + checksum: 28c5f7bbbcabc9dabd4117e8fdc61483f6872a1c6b02a4b1c4d68c5b79d06896c3cc9547610c4c3ba64658531caa2de13ead1ea1bf321c7b53e969c4752b98c7 + languageName: node + linkType: hard + "tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -2789,9 +3881,9 @@ __metadata: linkType: hard "tslib@npm:^2.1.0, tslib@npm:^2.5.0": - version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + version: 2.6.0 + resolution: "tslib@npm:2.6.0" + checksum: c01066038f950016a18106ddeca4649b4d76caa76ec5a31e2a26e10586a59fceb4ee45e96719bf6c715648e7c14085a81fee5c62f7e9ebee68e77a5396e5538f languageName: node linkType: hard @@ -2851,6 +3943,13 @@ __metadata: languageName: node linkType: hard +"type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 + languageName: node + linkType: hard + "type-fest@npm:^0.20.2": version: 0.20.2 resolution: "type-fest@npm:0.20.2" @@ -2878,30 +3977,46 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.1.2": + version: 1.1.2 + resolution: "ufo@npm:1.1.2" + checksum: 83c940a6a23b6d4fc0cd116265bb5dcf88ab34a408ad9196e413270ca607a4781c09b547dc518f43caee128a096f20fe80b5a0e62b4bcc0a868619896106d048 + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" + bin: + uglifyjs: bin/uglifyjs + checksum: 7b3897df38b6fc7d7d9f4dcd658599d81aa2b1fb0d074829dd4e5290f7318dbca1f4af2f45acb833b95b1fe0ed4698662ab61b87e94328eb4c0a0d3435baf924 + languageName: node + linkType: hard + "undici@npm:^5.22.0": - version: 5.22.0 - resolution: "undici@npm:5.22.0" + version: 5.22.1 + resolution: "undici@npm:5.22.1" dependencies: busboy: ^1.6.0 - checksum: 8dc55240a60ae7680798df344e8f46ad0f872ed0fa434fb94cc4fd2b5b2f8053bdf11994d15902999d3880f9bf7cd875a2e90883d2702bf0f366dacd9cbf3fc6 + checksum: 048a3365f622be44fb319316cedfaa241c59cf7f3368ae7667a12323447e1822e8cc3d00f6956c852d1478a6fde1cbbe753f49e05f2fdaed229693e716ebaf35 languageName: node linkType: hard -"unique-filename@npm:^2.0.0": - version: 2.0.1 - resolution: "unique-filename@npm:2.0.1" - dependencies: - unique-slug: ^3.0.0 - checksum: 807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f - languageName: node - linkType: hard - -"unique-slug@npm:^3.0.0": +"unique-filename@npm:^3.0.0": version: 3.0.0 - resolution: "unique-slug@npm:3.0.0" + resolution: "unique-filename@npm:3.0.0" + dependencies: + unique-slug: ^4.0.0 + checksum: 8e2f59b356cb2e54aab14ff98a51ac6c45781d15ceaab6d4f1c2228b780193dc70fae4463ce9e1df4479cb9d3304d7c2043a3fb905bdeca71cc7e8ce27e063df + languageName: node + linkType: hard + +"unique-slug@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-slug@npm:4.0.0" dependencies: imurmurhash: ^0.1.4 - checksum: 49f8d915ba7f0101801b922062ee46b7953256c93ceca74303bd8e6413ae10aa7e8216556b54dc5382895e8221d04f1efaf75f945c2e4a515b4139f77aa6640c + checksum: 0884b58365af59f89739e6f71e3feacb5b1b41f2df2d842d0757933620e6de08eff347d27e9d499b43c40476cbaf7988638d3acb2ffbcb9d35fd035591adfd15 languageName: node linkType: hard @@ -2928,6 +4043,131 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:0.33.0": + version: 0.33.0 + resolution: "vite-node@npm:0.33.0" + dependencies: + cac: ^6.7.14 + debug: ^4.3.4 + mlly: ^1.4.0 + pathe: ^1.1.1 + picocolors: ^1.0.0 + vite: ^3.0.0 || ^4.0.0 + bin: + vite-node: vite-node.mjs + checksum: 7c37911251d3e318fe4ad6b4093207498336ce190a58afb43a9ae701eee7f110ef80920b79061710cf6abcc6335ce58f6ca412ee6b268f25fe10f278c94cc264 + languageName: node + linkType: hard + +"vite@npm:^3.0.0 || ^4.0.0": + version: 4.4.2 + resolution: "vite@npm:4.4.2" + dependencies: + esbuild: ^0.18.10 + fsevents: ~2.3.2 + postcss: ^8.4.24 + rollup: ^3.25.2 + peerDependencies: + "@types/node": ">= 14" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 37a1c3a0ccc4b80c0a696203d3608d6bf7dc98219ddc178e528b972e3305af9ab8f92dea455421024e98c59372f6a97b1b4cc409a05f757a64bff830610053c6 + languageName: node + linkType: hard + +"vitest@npm:latest": + version: 0.33.0 + resolution: "vitest@npm:0.33.0" + dependencies: + "@types/chai": ^4.3.5 + "@types/chai-subset": ^1.3.3 + "@types/node": "*" + "@vitest/expect": 0.33.0 + "@vitest/runner": 0.33.0 + "@vitest/snapshot": 0.33.0 + "@vitest/spy": 0.33.0 + "@vitest/utils": 0.33.0 + acorn: ^8.9.0 + acorn-walk: ^8.2.0 + cac: ^6.7.14 + chai: ^4.3.7 + debug: ^4.3.4 + local-pkg: ^0.4.3 + magic-string: ^0.30.1 + pathe: ^1.1.1 + picocolors: ^1.0.0 + std-env: ^3.3.3 + strip-literal: ^1.0.1 + tinybench: ^2.5.0 + tinypool: ^0.6.0 + vite: ^3.0.0 || ^4.0.0 + vite-node: 0.33.0 + why-is-node-running: ^2.2.2 + peerDependencies: + "@edge-runtime/vm": "*" + "@vitest/browser": "*" + "@vitest/ui": "*" + happy-dom: "*" + jsdom: "*" + playwright: "*" + safaridriver: "*" + webdriverio: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + bin: + vitest: vitest.mjs + checksum: c1884b2a1a41af81ee54c86a986a32b6a4c69ec3b3f7d2322f92c8fad5532d6a12160e7efb7927e4c53d95806ef4ede9549bdd82c66604e281c71056212f56e7 + languageName: node + linkType: hard + +"watskeburt@npm:0.11.5": + version: 0.11.5 + resolution: "watskeburt@npm:0.11.5" + bin: + watskeburt: bin/cli.js + checksum: d4101995567b1a3ec27ff815d152b80b67e2a059e55c5bd808e4ea12f01269bdcdd553e0b2060185051f2be69509b23d8eac5ea1af8f23ce9456937a6cd9b852 + languageName: node + linkType: hard + "webidl-conversions@npm:^4.0.2": version: 4.0.2 resolution: "webidl-conversions@npm:4.0.2" @@ -2957,6 +4197,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: ^2.0.0 + stackback: 0.0.2 + bin: + why-is-node-running: cli.js + checksum: 50820428f6a82dfc3cbce661570bcae9b658723217359b6037b67e495255409b4c8bc7931745f5c175df71210450464517cab32b2f7458ac9c40b4925065200a + languageName: node + linkType: hard + "wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" @@ -2966,10 +4218,32 @@ __metadata: languageName: node linkType: hard -"word-wrap@npm:^1.2.3": - version: 1.2.3 - resolution: "word-wrap@npm:1.2.3" - checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 2a44b2788165d0a3de71fd517d4880a8e20ea3a82c080ce46e294f0b68b69a2e49cff5f99c600e275c698a90d12c5ea32aff06c311f0db2eb3f1201f3e7b2a04 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + +"wrap-ansi@npm:8.1.0, wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: ^6.1.0 + string-width: ^5.0.1 + strip-ansi: ^7.0.1 + checksum: 371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238 languageName: node linkType: hard @@ -3015,3 +4289,10 @@ __metadata: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node linkType: hard + +"yocto-queue@npm:^1.0.0": + version: 1.0.0 + resolution: "yocto-queue@npm:1.0.0" + checksum: 2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + languageName: node + linkType: hard