From 4a32f943b3ace9661297f9469d22fef51b0707cc Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sat, 8 Jun 2024 22:53:14 -0500 Subject: [PATCH 01/14] publisher first commit --- .gitignore | 1 + packages/publisher/index.ts | 133 ++++++++++++++++++++++++ packages/publisher/internal.ts | 0 packages/publisher/package.json | 26 +++++ packages/publisher/tsconfig.json | 113 +++++++++++++++++++++ yarn.lock | 167 ++++++++++++++++++++++++++++++- 6 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 packages/publisher/index.ts create mode 100644 packages/publisher/internal.ts create mode 100644 packages/publisher/package.json create mode 100644 packages/publisher/tsconfig.json diff --git a/.gitignore b/.gitignore index 2d53da6..4d31c57 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ node_modules/**/* packages/ioc/node_modules/* packages/poster/dts/discord.d.ts packages/**/node_modules +packages/**/dist diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts new file mode 100644 index 0000000..f149bca --- /dev/null +++ b/packages/publisher/index.ts @@ -0,0 +1,133 @@ +import type { Init, CommandModule, Emitter, Logging } from '@sern/handler' +import { controller, CommandInitPlugin } from '@sern/handler' + +const optionsTransformer = (ops: Array<{ type: number }>) => { + return ops.map((el) => { + if ('command' in el) { + const { command, ...rest } = el; + return rest; + } + return el; + }); +}; + +const intoApplicationType = (type: number) => + type === 3 ? 1 : Math.log2(type); + +const makeDescription = (type: number, desc: string) => { + if (type !== 1 && desc !== '') { + console.warn('Found context menu that has non empty description field. Implictly publishing with empty description'); + return ''; + } + return desc; +}; + +const serializePermissions = (permissions: unknown) => { + if(typeof permissions === 'bigint' || typeof permissions === 'number') { + return permissions.toString(); + } + + if(Array.isArray(permissions)) { + return permissions + .reduce((acc, cur) => acc | cur, BigInt(0)) + .toString() + } + return null; +} +const BASE_URL = new URL('https://discord.com/api/v10/applications/'); +const PUBLISHABLE = 0b1110; +const IS_GUILDED = Symbol.for('@@guilded') +const IS_GLOBAL = Symbol.for('@@global') + +export class Publisher implements Init { + constructor( + private modules: Map, + private sernEmitter : Emitter, + private logger: Logging + ) {} + + async init() { + const headers = { + Authorization: 'Bot ' + process.env.DISCORD_TOKEN, + 'Content-Type': 'application/json', + }; + let me; + let appid: string; + try { + me = await fetch(new URL('@me', BASE_URL), { headers }).then(res => res.json()); + appid = me.id; + } catch(e) { + console.log("Something went wrong while trying to fetch your application:"); + throw e; + } + const GLOBAL_URL = new URL(`${appid}/commands`, BASE_URL); + this.sernEmitter.addListener('modulesLoaded', () => { + this.logger.info({ message: 'publishing modules' }); + const modules = Array.from(this.modules.values()) + .filter(module => (module.type & PUBLISHABLE) != 0) + const [globalCommands, guildedCommands] = modules.reduce( + ([globals, guilded], module) => { + //@ts-ignore + const isPublishableGlobally = module[IS_GLOBAL]; + if (isPublishableGlobally) { + return [[module, ...globals], guilded]; + } + return [globals, [module, ...guilded]]; + }, [[], []] as [CommandModule[], CommandModule[]]); + }) + } +} + +type ValidMemberPermissions = + | string //must be a stringified number + | bigint + +interface PublishConfig { + guildIds?: string[]; + defaultMemberPermissions: ValidMemberPermissions; +} + +type ValidPublishOptions = + | PublishConfig + | ((absPath: string, module: CommandModule) => PublishConfig) + + +export const serialize = (config: ValidPublishOptions) => { + + return CommandInitPlugin(({ module, absPath }) => { + let _config=config + if(typeof _config === 'function') { + _config = _config(absPath, module); + } + if(_config.guildIds) { + Reflect.set(module, IS_GUILDED, true) + } else { + Reflect.set(module, IS_GLOBAL, true) + } + Reflect.set(module, 'toJSON', function () { + const applicationType = intoApplicationType(module.type); + return { + name: module.name, + type: applicationType, + description: makeDescription(applicationType, module.description), + options: optionsTransformer(module?.options ?? []), + default_member_permissions: serializePermissions(config?.defaultMemberPermissions), + //@ts-ignore + integration_types: (config?.integrationTypes ?? ['Guild']).map( + (s: string) => { + if(s === "Guild") { + return "0"; + } else if (s == "User") { + return "1"; + } else { + throw Error("IntegrationType is not one of Guild or User"); + } + }), + //@ts-ignore + contexts: config?.contexts ? config.contexts : undefined + } + }) + return controller.next(); + }) +} + diff --git a/packages/publisher/internal.ts b/packages/publisher/internal.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/publisher/package.json b/packages/publisher/package.json new file mode 100644 index 0000000..16bcb3f --- /dev/null +++ b/packages/publisher/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sern/publisher", + "version": "1.1.0", + "description": "Localizer", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "watch": "tsc --watch", + "test": "vitest --run" + }, + "devDependencies": { + "@sern/handler": "^3.3.0", + "discord.js": "^14.15.3", + "vitest": "^1.2.2" + }, + "keywords": [], + "author": "", + "license": "ISC", + "publishConfig": { + "access": "public", + "tag": "alpha" + }, + "dependencies": { + "@parcel/watcher": "^2.4.1" + } +} diff --git a/packages/publisher/tsconfig.json b/packages/publisher/tsconfig.json new file mode 100644 index 0000000..d5ec88a --- /dev/null +++ b/packages/publisher/tsconfig.json @@ -0,0 +1,113 @@ +{ + "files": [ + "./index.ts", + "./internal.ts" + ], + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "CommonJS", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/yarn.lock b/yarn.lock index 7d2288b..bafcdaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -331,6 +331,140 @@ __metadata: languageName: node linkType: hard +"@parcel/watcher-android-arm64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-android-arm64@npm:2.4.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-arm64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-darwin-arm64@npm:2.4.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-x64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-darwin-x64@npm:2.4.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-freebsd-x64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-freebsd-x64@npm:2.4.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm-glibc@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-arm-glibc@npm:2.4.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-glibc@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.4.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-musl@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-arm64-musl@npm:2.4.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-glibc@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-x64-glibc@npm:2.4.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-musl@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-x64-musl@npm:2.4.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-win32-arm64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-win32-arm64@npm:2.4.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-win32-ia32@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-win32-ia32@npm:2.4.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@parcel/watcher-win32-x64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-win32-x64@npm:2.4.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher@npm:^2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher@npm:2.4.1" + dependencies: + "@parcel/watcher-android-arm64": 2.4.1 + "@parcel/watcher-darwin-arm64": 2.4.1 + "@parcel/watcher-darwin-x64": 2.4.1 + "@parcel/watcher-freebsd-x64": 2.4.1 + "@parcel/watcher-linux-arm-glibc": 2.4.1 + "@parcel/watcher-linux-arm64-glibc": 2.4.1 + "@parcel/watcher-linux-arm64-musl": 2.4.1 + "@parcel/watcher-linux-x64-glibc": 2.4.1 + "@parcel/watcher-linux-x64-musl": 2.4.1 + "@parcel/watcher-win32-arm64": 2.4.1 + "@parcel/watcher-win32-ia32": 2.4.1 + "@parcel/watcher-win32-x64": 2.4.1 + detect-libc: ^1.0.3 + is-glob: ^4.0.3 + micromatch: ^4.0.5 + node-addon-api: ^7.0.0 + node-gyp: latest + dependenciesMeta: + "@parcel/watcher-android-arm64": + optional: true + "@parcel/watcher-darwin-arm64": + optional: true + "@parcel/watcher-darwin-x64": + optional: true + "@parcel/watcher-freebsd-x64": + optional: true + "@parcel/watcher-linux-arm-glibc": + optional: true + "@parcel/watcher-linux-arm64-glibc": + optional: true + "@parcel/watcher-linux-arm64-musl": + optional: true + "@parcel/watcher-linux-x64-glibc": + optional: true + "@parcel/watcher-linux-x64-musl": + optional: true + "@parcel/watcher-win32-arm64": + optional: true + "@parcel/watcher-win32-ia32": + optional: true + "@parcel/watcher-win32-x64": + optional: true + checksum: 4da70551da27e565c726b0bbd5ba5afcb2bca36dfd8619a649f0eaa41f693ddd1d630c36e53bc083895d71a3e28bc4199013e557cd13c7af6ccccab28ceecbff + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -522,6 +656,17 @@ __metadata: languageName: unknown linkType: soft +"@sern/publisher@workspace:packages/publisher": + version: 0.0.0-use.local + resolution: "@sern/publisher@workspace:packages/publisher" + dependencies: + "@parcel/watcher": ^2.4.1 + "@sern/handler": ^3.3.0 + discord.js: ^14.15.3 + vitest: ^1.2.2 + languageName: unknown + linkType: soft + "@sinclair/typebox@npm:^0.27.8": version: 0.27.8 resolution: "@sinclair/typebox@npm:0.27.8" @@ -921,6 +1066,15 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: daaaed925ffa7889bd91d56e9624e6c8033911bb60f3a50a74a87500680652969dbaab9526d1e200a4c94acf80fc862a22131841145a0a8482d60a99c24f4a3e + languageName: node + linkType: hard + "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -1339,7 +1493,7 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.1, is-glob@npm:~4.0.1": +"is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" dependencies: @@ -1520,7 +1674,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.4": +"micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": version: 4.0.7 resolution: "micromatch@npm:4.0.7" dependencies: @@ -1674,6 +1828,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^7.0.0": + version: 7.1.0 + resolution: "node-addon-api@npm:7.1.0" + dependencies: + node-gyp: latest + checksum: 26640c8d2ed7e2059e2ed65ee79e2a195306b3f1fc27ad11448943ba91d37767bd717a9a0453cc97e83a1109194dced8336a55f8650000458ef625c0b8b5e3df + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 10.1.0 resolution: "node-gyp@npm:10.1.0" From 200595b688fc9c06dd5f9cad580571e2fd0e3d02 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 9 Jun 2024 20:13:49 -0500 Subject: [PATCH 02/14] identical to cli now --- packages/publisher/index.ts | 157 +++++++++++++++++++++++-------- packages/publisher/internal.ts | 0 packages/publisher/tsconfig.json | 3 +- 3 files changed, 120 insertions(+), 40 deletions(-) delete mode 100644 packages/publisher/internal.ts diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index f149bca..6db8b25 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -1,14 +1,17 @@ import type { Init, CommandModule, Emitter, Logging } from '@sern/handler' import { controller, CommandInitPlugin } from '@sern/handler' +import { writeFile } from 'node:fs/promises'; +import { inspect } from 'node:util'; -const optionsTransformer = (ops: Array<{ type: number }>) => { - return ops.map((el) => { + +const optionsTransformer = (ops?: Array<{ type: number }>) => { + return ops?.map((el) => { if ('command' in el) { const { command, ...rest } = el; return rest; } return el; - }); + }) ?? []; }; const intoApplicationType = (type: number) => @@ -36,9 +39,8 @@ const serializePermissions = (permissions: unknown) => { } const BASE_URL = new URL('https://discord.com/api/v10/applications/'); const PUBLISHABLE = 0b1110; -const IS_GUILDED = Symbol.for('@@guilded') -const IS_GLOBAL = Symbol.for('@@global') - +const GUILD_IDS = Symbol.for('GUILD_IDS') +const PUBLISH = Symbol.for('@sern/publish') export class Publisher implements Init { constructor( private modules: Map, @@ -47,6 +49,9 @@ export class Publisher implements Init { ) {} async init() { + if(!process.env.DISCORD_TOKEN) { + throw Error("No token found to publish. add DISCORD_TOKEN to .env"); + } const headers = { Authorization: 'Bot ' + process.env.DISCORD_TOKEN, 'Content-Type': 'application/json', @@ -61,19 +66,115 @@ export class Publisher implements Init { throw e; } const GLOBAL_URL = new URL(`${appid}/commands`, BASE_URL); - this.sernEmitter.addListener('modulesLoaded', () => { + + const listener = async () => { this.logger.info({ message: 'publishing modules' }); - const modules = Array.from(this.modules.values()) - .filter(module => (module.type & PUBLISHABLE) != 0) + const modules = + Array.from(this.modules.values()) + .filter(module => (module.type & PUBLISHABLE) != 0) + .map(module => { + return { + //@ts-ignore + [PUBLISH]: module[PUBLISH], + toJSON() { + const applicationType = intoApplicationType(module.type); + //@ts-ignore + const { defaultMemberPermissions, integrationTypes, contexts } = module[PUBLISH] ?? {}; + return { + name: module.name, type: applicationType, + //@ts-ignore + description: makeDescription(applicationType, module.description), + //@ts-ignore shutup + options: optionsTransformer(module?.options), + default_member_permissions: serializePermissions(defaultMemberPermissions), + integration_types: (integrationTypes ?? ['Guild']).map( + (s: string) => { + if(s === "Guild") return "0"; + else if (s == "User") return "1"; + else throw Error("IntegrationType is not one of Guild or User"); + }), + contexts, + //@ts-ignore + name_localizations: module.name_localizations, + //@ts-ignore + description_localizations: module.description_localizations + } + } + } + }) const [globalCommands, guildedCommands] = modules.reduce( ([globals, guilded], module) => { //@ts-ignore - const isPublishableGlobally = module[IS_GLOBAL]; + const isPublishableGlobally = !module[PUBLISH]?.[GUILD_IDS]; if (isPublishableGlobally) { return [[module, ...globals], guilded]; } return [globals, [module, ...guilded]]; - }, [[], []] as [CommandModule[], CommandModule[]]); + }, [[], []] as [any[], any[]]); + + const resultGlobal = await fetch(GLOBAL_URL, { + method: 'PUT', + headers, + body: JSON.stringify(globalCommands) + }) + if(resultGlobal.ok) { + this.logger.info({ message: "GLOBAL: OK" }) + } else { + this.logger.info({ message: inspect(await resultGlobal.json(), false, Infinity ) }) + } + const guildIdMap: Map = new Map(); + const responsesMap = new Map(); + guildedCommands.forEach((entry) => { + const guildIds: string[] = entry[GUILD_IDS] ?? []; + if (guildIds) { + guildIds.forEach((guildId) => { + if (guildIdMap.has(guildId)) { + guildIdMap.get(guildId)?.push(entry); + } else { + guildIdMap.set(guildId, [entry]); + } + }); + } + }); + for (const [guildId, array] of guildIdMap.entries()) { + const guildCommandURL = new URL(`${appid}/guilds/${guildId}/commands`, BASE_URL); + const response = await fetch(guildCommandURL, { + method: 'PUT', + body: JSON.stringify(array), + headers, + }); + const result = await response.json(); + if (response.ok) { + this.logger.info({ message: guildId + " published succesfully" }) + responsesMap.set(guildId, result); + } else { + switch(response.status) { + case 400 : { + console.error(inspect(result, { depth: Infinity })) + console.error("Modules with validation errors:" + + inspect(Object.keys(result.errors).map(idx => array[idx as any]))) + throw Error("400: Ensure your commands have proper fields and data and nothing left out"); + } + case 404 : { + console.error(inspect(result, { depth: Infinity })) + throw Error("Forbidden 404. Is you application id and/or token correct?") + } + case 429: { + console.error(inspect(result, { depth: Infinity })) + throw Error('Chill out homie, too many requests') + } + } + } + } + await writeFile( + '.sern/command-data-remote.json', + JSON.stringify({ global: await resultGlobal.json(), + ...Object.fromEntries(responsesMap) }, null, 4), + 'utf8') + } + this.sernEmitter.addListener('modulesLoaded', () => { + listener(); + this.sernEmitter.removeListener('modulesLoaded', listener); }) } } @@ -95,37 +196,17 @@ type ValidPublishOptions = export const serialize = (config: ValidPublishOptions) => { return CommandInitPlugin(({ module, absPath }) => { + if((module.type & PUBLISHABLE) === 0) { + //@ts-ignore + return controller.stop("Cannot publish this module"); + } let _config=config if(typeof _config === 'function') { _config = _config(absPath, module); } - if(_config.guildIds) { - Reflect.set(module, IS_GUILDED, true) - } else { - Reflect.set(module, IS_GLOBAL, true) - } - Reflect.set(module, 'toJSON', function () { - const applicationType = intoApplicationType(module.type); - return { - name: module.name, - type: applicationType, - description: makeDescription(applicationType, module.description), - options: optionsTransformer(module?.options ?? []), - default_member_permissions: serializePermissions(config?.defaultMemberPermissions), - //@ts-ignore - integration_types: (config?.integrationTypes ?? ['Guild']).map( - (s: string) => { - if(s === "Guild") { - return "0"; - } else if (s == "User") { - return "1"; - } else { - throw Error("IntegrationType is not one of Guild or User"); - } - }), - //@ts-ignore - contexts: config?.contexts ? config.contexts : undefined - } + //adding extra configuration + Reflect.set(module, PUBLISH, { + [GUILD_IDS]: _config.guildIds, }) return controller.next(); }) diff --git a/packages/publisher/internal.ts b/packages/publisher/internal.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/publisher/tsconfig.json b/packages/publisher/tsconfig.json index d5ec88a..36da4e8 100644 --- a/packages/publisher/tsconfig.json +++ b/packages/publisher/tsconfig.json @@ -1,7 +1,6 @@ { "files": [ - "./index.ts", - "./internal.ts" + "./index.ts" ], "compilerOptions": { /* Visit https://aka.ms/tsconfig to read more about this file */ From a9d9006755a06329759a3a9026caf616d3078ec1 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:18:25 -0500 Subject: [PATCH 03/14] export more typings --- packages/publisher/index.ts | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 6db8b25..850b46a 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -2,7 +2,7 @@ import type { Init, CommandModule, Emitter, Logging } from '@sern/handler' import { controller, CommandInitPlugin } from '@sern/handler' import { writeFile } from 'node:fs/promises'; import { inspect } from 'node:util'; - +import type { PermissionFlagsBits } from 'discord.js' const optionsTransformer = (ops?: Array<{ type: number }>) => { return ops?.map((el) => { @@ -78,16 +78,17 @@ export class Publisher implements Init { [PUBLISH]: module[PUBLISH], toJSON() { const applicationType = intoApplicationType(module.type); - //@ts-ignore - const { defaultMemberPermissions, integrationTypes, contexts } = module[PUBLISH] ?? {}; + const { default_member_permissions, + integration_types,//@ts-ignore + contexts } = module[PUBLISH] ?? {}; return { name: module.name, type: applicationType, //@ts-ignore description: makeDescription(applicationType, module.description), //@ts-ignore shutup options: optionsTransformer(module?.options), - default_member_permissions: serializePermissions(defaultMemberPermissions), - integration_types: (integrationTypes ?? ['Guild']).map( + default_member_permissions, + integration_types: (integration_types ?? ['Guild']).map( (s: string) => { if(s === "Guild") return "0"; else if (s == "User") return "1"; @@ -118,7 +119,7 @@ export class Publisher implements Init { body: JSON.stringify(globalCommands) }) if(resultGlobal.ok) { - this.logger.info({ message: "GLOBAL: OK" }) + this.logger.info({ message: "published all global commands" }) } else { this.logger.info({ message: inspect(await resultGlobal.json(), false, Infinity ) }) } @@ -179,21 +180,25 @@ export class Publisher implements Init { } } -type ValidMemberPermissions = +export type ValidMemberPermissions = + | typeof PermissionFlagsBits //discord.js enum + | Array | string //must be a stringified number | bigint -interface PublishConfig { +export interface PublishConfig { guildIds?: string[]; - defaultMemberPermissions: ValidMemberPermissions; + defaultMemberPermissions?: ValidMemberPermissions; + integrationTypes?: Array<'Guild'|'User'> + contexts: number[] } -type ValidPublishOptions = +export type ValidPublishOptions = | PublishConfig | ((absPath: string, module: CommandModule) => PublishConfig) -export const serialize = (config: ValidPublishOptions) => { +export const publishConfig = (config: ValidPublishOptions) => { return CommandInitPlugin(({ module, absPath }) => { if((module.type & PUBLISHABLE) === 0) { @@ -204,9 +209,13 @@ export const serialize = (config: ValidPublishOptions) => { if(typeof _config === 'function') { _config = _config(absPath, module); } + const { contexts, defaultMemberPermissions, integrationTypes } = _config //adding extra configuration Reflect.set(module, PUBLISH, { [GUILD_IDS]: _config.guildIds, + default_member_permissions: serializePermissions(defaultMemberPermissions), + integration_types: integrationTypes, + contexts }) return controller.next(); }) From 51178f95c257f46d918d85110b261ee41e4401ea Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Sun, 9 Jun 2024 23:14:14 -0500 Subject: [PATCH 04/14] more stuff --- packages/publisher/index.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 850b46a..fc65efc 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -37,6 +37,7 @@ const serializePermissions = (permissions: unknown) => { } return null; } + const BASE_URL = new URL('https://discord.com/api/v10/applications/'); const PUBLISHABLE = 0b1110; const GUILD_IDS = Symbol.for('GUILD_IDS') @@ -56,8 +57,7 @@ export class Publisher implements Init { Authorization: 'Bot ' + process.env.DISCORD_TOKEN, 'Content-Type': 'application/json', }; - let me; - let appid: string; + let me; let appid: string; try { me = await fetch(new URL('@me', BASE_URL), { headers }).then(res => res.json()); appid = me.id; @@ -105,7 +105,6 @@ export class Publisher implements Init { }) const [globalCommands, guildedCommands] = modules.reduce( ([globals, guilded], module) => { - //@ts-ignore const isPublishableGlobally = !module[PUBLISH]?.[GUILD_IDS]; if (isPublishableGlobally) { return [[module, ...globals], guilded]; @@ -118,10 +117,12 @@ export class Publisher implements Init { headers, body: JSON.stringify(globalCommands) }) + const globalJsonBody = await resultGlobal.json(); if(resultGlobal.ok) { this.logger.info({ message: "published all global commands" }) } else { - this.logger.info({ message: inspect(await resultGlobal.json(), false, Infinity ) }) + this.logger.info({ message: inspect(globalJsonBody, false, Infinity ) }) + //todo: implement rate limiting } const guildIdMap: Map = new Map(); const responsesMap = new Map(); @@ -167,9 +168,10 @@ export class Publisher implements Init { } } } + await writeFile( '.sern/command-data-remote.json', - JSON.stringify({ global: await resultGlobal.json(), + JSON.stringify({ global: globalJsonBody, ...Object.fromEntries(responsesMap) }, null, 4), 'utf8') } From 238aaaf7f335fc9c5ff7d3a3f423f9dacbac1f13 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 10 Jun 2024 00:21:02 -0500 Subject: [PATCH 05/14] update docs and add for published and fix guild publiushing --- packages/localizer/index.mdx | 2 +- packages/publisher/index.mdx | 106 +++++++++++++++++++++++++++++++++++ packages/publisher/index.ts | 30 +++++----- 3 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 packages/publisher/index.mdx diff --git a/packages/localizer/index.mdx b/packages/localizer/index.mdx index eda90fa..500aeba 100644 --- a/packages/localizer/index.mdx +++ b/packages/localizer/index.mdx @@ -2,7 +2,7 @@ title: Localizer description: Translate your bot for the world sidebar: - order: 1 + order: 2 --- diff --git a/packages/publisher/index.mdx b/packages/publisher/index.mdx new file mode 100644 index 0000000..734ac6f --- /dev/null +++ b/packages/publisher/index.mdx @@ -0,0 +1,106 @@ +--- +title: Publisher +description: Publish application commands as a Service +sidebar: + order: 1 +--- + +## Implicits +- Requires process.env to be populated +- A common provider of this is `dotenv` +```txt title=".env" +DISCORD_TOKEN= +APPLICATION_ID= +NODE_ENV= +``` +- Calls the discord API with the [PUT route](https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands). Wherever your commands directory is located, publish will override the existing application commands at Discord. + +## Usage + +**Initializing the Publisher** +```ts +import { makeDependencies } from '@sern/handler'; +import { Publisher } from '@sern/localizer'; + +await makeDependencies(({ add }) => { + add('publisher', new Publisher()); +}); +``` + + + +## Features +- Automatically syncs api with your command base +- generates JSON file of output (**.sern/command-data-remote.json**) +- supports a configuration that is the same as the original publish plugin. + + +Each command file can have an extra plugin `publishConfig` that follows `ValidPublishOptions`: +## Config +```ts + +type ValidMemberPermissions = + | typeof PermissionFlagBits //discord.js enum + | typeof PermissionFlagBits[] //array of discord.js enum + | string //must be a stringified number + | bigint + +interface PublishConfig { + guildIds?: string[]; + defaultMemberPermissions?: ValidMemberPermissions; + integrationTypes?: Array<'Guild'|'User'> + contexts: number[] +} +type ValidPublishOptions = + | PublishConfig + | (absPath: string, module: CommandModule) => PublishConfig +``` + +### Example: command published with integrationTypes + +:::tip +Make sure you modify the install method in the Discord dev portal +::: + +```ts title=src/commands/ping.ts +import { commandModule, CommandType } from '@sern/handler' +import { publishConfig } from '@sern/publisher' + +export default commandModule( { + type: CommandType.Slash, + plugins: [ + publishConfig({ + integrationTypes: ['User'], + contexts: [1,2] + }) + ], + description: `hello worl`, + execute: (ctx) => { + ctx.reply('pong') + } +}) + +``` + + +### Example: command published in guild + +```ts title=src/commands/ping.ts +import { commandModule, CommandType } from '@sern/handler' +import { publishConfig } from '@sern/publisher' + +export default commandModule( { + type: CommandType.Slash, + plugins: [ + publishConfig({ + guildIds: ["889026545715400705"] + }) + ], + description: `hello worl`, + execute: (ctx) => { + ctx.reply('pong') + } +}) + +``` + diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index fc65efc..4a5726d 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -105,7 +105,7 @@ export class Publisher implements Init { }) const [globalCommands, guildedCommands] = modules.reduce( ([globals, guilded], module) => { - const isPublishableGlobally = !module[PUBLISH]?.[GUILD_IDS]; + const isPublishableGlobally = !module[PUBLISH] || !Array.isArray(module[PUBLISH].guildIds); if (isPublishableGlobally) { return [[module, ...globals], guilded]; } @@ -127,7 +127,7 @@ export class Publisher implements Init { const guildIdMap: Map = new Map(); const responsesMap = new Map(); guildedCommands.forEach((entry) => { - const guildIds: string[] = entry[GUILD_IDS] ?? []; + const guildIds: string[] = entry[PUBLISH].guildIds ?? []; if (guildIds) { guildIds.forEach((guildId) => { if (guildIdMap.has(guildId)) { @@ -192,7 +192,7 @@ export interface PublishConfig { guildIds?: string[]; defaultMemberPermissions?: ValidMemberPermissions; integrationTypes?: Array<'Guild'|'User'> - contexts: number[] + contexts?: number[] } export type ValidPublishOptions = @@ -208,18 +208,18 @@ export const publishConfig = (config: ValidPublishOptions) => { return controller.stop("Cannot publish this module"); } let _config=config - if(typeof _config === 'function') { - _config = _config(absPath, module); - } - const { contexts, defaultMemberPermissions, integrationTypes } = _config - //adding extra configuration - Reflect.set(module, PUBLISH, { - [GUILD_IDS]: _config.guildIds, - default_member_permissions: serializePermissions(defaultMemberPermissions), - integration_types: integrationTypes, - contexts - }) - return controller.next(); + if(typeof _config === 'function') { + _config = _config(absPath, module); + } + const { contexts, defaultMemberPermissions, integrationTypes } = _config + //adding extra configuration + Reflect.set(module, PUBLISH, { + guildIds: _config.guildIds, + default_member_permissions: serializePermissions(defaultMemberPermissions), + integration_types: integrationTypes, + contexts + }) + return controller.next(); }) } From a8350d9da690dd427641826b69dd06946435aa25 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:24:31 -0500 Subject: [PATCH 06/14] metadata --- packages/publisher/metadata.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/publisher/metadata.json diff --git a/packages/publisher/metadata.json b/packages/publisher/metadata.json new file mode 100644 index 0000000..f744f73 --- /dev/null +++ b/packages/publisher/metadata.json @@ -0,0 +1 @@ +{ "type" : "service" } From ed36814ec985b99a24a6174164dcbe05e1f89dec Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:39:44 -0500 Subject: [PATCH 07/14] update docs for localizer and pubsliher --- packages/localizer/index.mdx | 6 +++--- packages/localizer/index.ts | 6 +++--- packages/publisher/index.ts | 21 ++++++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/localizer/index.mdx b/packages/localizer/index.mdx index 500aeba..0c3f3ad 100644 --- a/packages/localizer/index.mdx +++ b/packages/localizer/index.mdx @@ -34,7 +34,7 @@ import { Tabs, TabItem } from "@astrojs/starlight/components"; - ```json title=~/assets/locals/es.json + ```json title=~/assets/locals/es-ES.json { "salute": { "hello": "hola" @@ -62,7 +62,7 @@ execute : (ctx, { deps }) => { //the localizer object from makeDependencies deps.localizer // Returns the Spanish translation for 'salute.hello' - deps.localizer.translate("salute.hello", "es"); + deps.localizer.translate("salute.hello", "es-ES"); } ``` @@ -71,5 +71,5 @@ execute : (ctx, { deps }) => { import { local } from '@sern/localizer'; // Returns the Spanish translation for 'salute.hello' -const greeting = local('salute.hello', 'es'); +const greeting = local('salute.hello', 'es-ES'); ``` diff --git a/packages/localizer/index.ts b/packages/localizer/index.ts index a926c7d..02c0ee3 100644 --- a/packages/localizer/index.ts +++ b/packages/localizer/index.ts @@ -54,10 +54,10 @@ class ShrimpleLocalizer implements Init { * Note: this method only works AFTER your container has been initiated * @example * ```ts - * assert.deepEqual(locals("salute.hello", "es"), "hola") + * assert.deepEqual(locals("salute.hello", "es-ES"), "hola") * ``` */ -export const local = (i: string, local: string) => { +export const local = (i: string, local: string) => { return Service('localizer').translate(i, local) } @@ -68,7 +68,7 @@ export const local = (i: string, local: string) => { * An init plugin to add localization fields to a command module. * Your localization configuration should look like, * @param root {string} If you have conflicting command names, you may configure the root of the name. (= command/{root}) - * Below is es.json (spanish) + * Below is es-ES.json (spanish) * ```json { "command/comer" : { diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 4a5726d..48677cb 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -40,14 +40,11 @@ const serializePermissions = (permissions: unknown) => { const BASE_URL = new URL('https://discord.com/api/v10/applications/'); const PUBLISHABLE = 0b1110; -const GUILD_IDS = Symbol.for('GUILD_IDS') const PUBLISH = Symbol.for('@sern/publish') export class Publisher implements Init { - constructor( - private modules: Map, - private sernEmitter : Emitter, - private logger: Logging - ) {} + constructor(private modules: Map, + private sernEmitter : Emitter, + private logger: Logging) {} async init() { if(!process.env.DISCORD_TOKEN) { @@ -168,7 +165,9 @@ export class Publisher implements Init { } } } - + this.logger.info({ + message: "Result of publishing is located at .sern/command-data-remote.json.\n Do not remove this!" + }); await writeFile( '.sern/command-data-remote.json', JSON.stringify({ global: globalJsonBody, @@ -199,9 +198,13 @@ export type ValidPublishOptions = | PublishConfig | ((absPath: string, module: CommandModule) => PublishConfig) - +/** + * the publishConfig plugin. + * If your commandModule requires extra properties such as publishing for certain guilds, you would + * put those options in there. + * @param {ValidPublishOptions} config options to configure how this module is published + */ export const publishConfig = (config: ValidPublishOptions) => { - return CommandInitPlugin(({ module, absPath }) => { if((module.type & PUBLISHABLE) === 0) { //@ts-ignore From 6a2709f52b783721fab9bec2a459609da50b9df3 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:16:12 -0500 Subject: [PATCH 08/14] yea --- packages/publisher/index.mdx | 3 --- packages/publisher/index.ts | 3 --- 2 files changed, 6 deletions(-) diff --git a/packages/publisher/index.mdx b/packages/publisher/index.mdx index 734ac6f..2d89e25 100644 --- a/packages/publisher/index.mdx +++ b/packages/publisher/index.mdx @@ -10,7 +10,6 @@ sidebar: - A common provider of this is `dotenv` ```txt title=".env" DISCORD_TOKEN= -APPLICATION_ID= NODE_ENV= ``` - Calls the discord API with the [PUT route](https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands). Wherever your commands directory is located, publish will override the existing application commands at Discord. @@ -27,8 +26,6 @@ await makeDependencies(({ add }) => { }); ``` - - ## Features - Automatically syncs api with your command base - generates JSON file of output (**.sern/command-data-remote.json**) diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 48677cb..cd91d3d 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -165,9 +165,6 @@ export class Publisher implements Init { } } } - this.logger.info({ - message: "Result of publishing is located at .sern/command-data-remote.json.\n Do not remove this!" - }); await writeFile( '.sern/command-data-remote.json', JSON.stringify({ global: globalJsonBody, From af3a703271f9eb978b65103a983e78bbbf5987b7 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Tue, 11 Jun 2024 17:09:19 -0500 Subject: [PATCH 09/14] add tsignores --- packages/localizer/index.ts | 2 +- packages/publisher/index.ts | 1 + yarn.lock | 9 ++++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/localizer/index.ts b/packages/localizer/index.ts index 02c0ee3..fc36d69 100644 --- a/packages/localizer/index.ts +++ b/packages/localizer/index.ts @@ -91,12 +91,12 @@ export const localize = (root?: string) => const resolvedLocalization= 'command/'+(root??module.name); Reflect.set(module, 'name_localizations', deps.localizer.translationsFor(resolvedLocalization+".name")); Reflect.set(module, 'description_localizations', deps.localizer.translationsFor(resolvedLocalization+'.description')); + //@ts-ignore const newOpts = module.options ?? []; //@ts-ignore dfsApplyLocalization(newOpts, deps, [resolvedLocalization]); return controller.next(); } else { - //@ts-ignore return controller.stop("Cannot localize this type of module " + module.name); } }) diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index cd91d3d..2183f50 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -209,6 +209,7 @@ export const publishConfig = (config: ValidPublishOptions) => { } let _config=config if(typeof _config === 'function') { + //@ts-ignore fix later _config = _config(absPath, module); } const { contexts, defaultMemberPermissions, integrationTypes } = _config diff --git a/yarn.lock b/yarn.lock index bafcdaa..7f51199 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2415,13 +2415,20 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.6.2, tslib@npm:^2.1.0, tslib@npm:^2.6.2": +"tslib@npm:2.6.2, tslib@npm:^2.6.2": version: 2.6.2 resolution: "tslib@npm:2.6.2" checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad languageName: node linkType: hard +"tslib@npm:^2.1.0": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 74fce0e100f1ebd95b8995fbbd0e6c91bdd8f4c35c00d4da62e285a3363aaa534de40a80db30ecfd388ed7c313c42d930ee0eaf108e8114214b180eec3dbe6f5 + languageName: node + linkType: hard + "type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" From 9036fee1837f10721473bd985b95430040ab475e Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Thu, 13 Jun 2024 00:02:25 -0500 Subject: [PATCH 10/14] a little more clear on type --- packages/publisher/index.ts | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 2183f50..ff5523d 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -1,5 +1,5 @@ import type { Init, CommandModule, Emitter, Logging } from '@sern/handler' -import { controller, CommandInitPlugin } from '@sern/handler' +import { controller, CommandInitPlugin, CommandType } from '@sern/handler' import { writeFile } from 'node:fs/promises'; import { inspect } from 'node:util'; import type { PermissionFlagsBits } from 'discord.js' @@ -18,22 +18,21 @@ const intoApplicationType = (type: number) => type === 3 ? 1 : Math.log2(type); const makeDescription = (type: number, desc: string) => { - if (type !== 1 && desc !== '') { + if (type !== CommandType.Text && desc !== '') { console.warn('Found context menu that has non empty description field. Implictly publishing with empty description'); return ''; } return desc; }; -const serializePermissions = (permissions: unknown) => { - if(typeof permissions === 'bigint' || typeof permissions === 'number') { - return permissions.toString(); +const serializePermissions = (perms: unknown) => { + if(typeof perms === 'bigint' || typeof perms === 'number') { + return perms.toString(); } - if(Array.isArray(permissions)) { - return permissions - .reduce((acc, cur) => acc | cur, BigInt(0)) - .toString() + if(Array.isArray(perms)) { + return perms.reduce((acc, cur) => acc|cur, BigInt(0)) + .toString() } return null; } @@ -41,6 +40,8 @@ const serializePermissions = (permissions: unknown) => { const BASE_URL = new URL('https://discord.com/api/v10/applications/'); const PUBLISHABLE = 0b1110; const PUBLISH = Symbol.for('@sern/publish') + + export class Publisher implements Init { constructor(private modules: Map, private sernEmitter : Emitter, @@ -50,11 +51,10 @@ export class Publisher implements Init { if(!process.env.DISCORD_TOKEN) { throw Error("No token found to publish. add DISCORD_TOKEN to .env"); } - const headers = { - Authorization: 'Bot ' + process.env.DISCORD_TOKEN, - 'Content-Type': 'application/json', - }; - let me; let appid: string; + const headers = [['Authorization', 'Bot ' + process.env.DISCORD_TOKEN], + ['Content-Type', 'application/json']] as Array<[string,string]> + let me; + let appid: string; try { me = await fetch(new URL('@me', BASE_URL), { headers }).then(res => res.json()); appid = me.id; @@ -76,16 +76,16 @@ export class Publisher implements Init { toJSON() { const applicationType = intoApplicationType(module.type); const { default_member_permissions, - integration_types,//@ts-ignore + integration_types=['Guild'],//@ts-ignore contexts } = module[PUBLISH] ?? {}; return { name: module.name, type: applicationType, - //@ts-ignore + //@ts-ignore we know description is at least empty str or filled description: makeDescription(applicationType, module.description), //@ts-ignore shutup options: optionsTransformer(module?.options), default_member_permissions, - integration_types: (integration_types ?? ['Guild']).map( + integration_types: integration_types.map( (s: string) => { if(s === "Guild") return "0"; else if (s == "User") return "1"; @@ -116,7 +116,7 @@ export class Publisher implements Init { }) const globalJsonBody = await resultGlobal.json(); if(resultGlobal.ok) { - this.logger.info({ message: "published all global commands" }) + this.logger.info({ message: "Publisher: All global commands published." }) } else { this.logger.info({ message: inspect(globalJsonBody, false, Infinity ) }) //todo: implement rate limiting @@ -209,7 +209,6 @@ export const publishConfig = (config: ValidPublishOptions) => { } let _config=config if(typeof _config === 'function') { - //@ts-ignore fix later _config = _config(absPath, module); } const { contexts, defaultMemberPermissions, integrationTypes } = _config From 1bcea21f89b643ba5b5d5a96c815e7080d1b71b1 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Fri, 14 Jun 2024 00:25:59 -0500 Subject: [PATCH 11/14] trying new api --- packages/ioc/src/container.ts | 75 +++++++++++++++++++++++++++++++++++ packages/ioc/tsconfig.json | 3 +- packages/localizer/index.ts | 19 +++++---- packages/publisher/index.ts | 31 ++++++++------- 4 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 packages/ioc/src/container.ts diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts new file mode 100644 index 0000000..61b5393 --- /dev/null +++ b/packages/ioc/src/container.ts @@ -0,0 +1,75 @@ + +function hasCallableMethod(obj: object, name: PropertyKey) { + // object will always be defined + // @ts-ignore + return typeof obj[name] == 'function'; +} +/** + * A Depedency injection container capable of adding singletons, firing hooks, and managing IOC within an application + */ +export class Container { + private __singletons = new Map(); + private hooks= new Map(); + private finished_init = false; + constructor(options: { autowire: boolean; path?: string }) { + if(options.autowire) { /* noop */ } + } + + addHook(name: string, callback: Function) { + if (!this.hooks.has(name)) { + this.hooks.set(name, []); + } + this.hooks.get(name)!.push(callback); + } + private registerHooks(hookname: string, insert: object) { + if(hasCallableMethod(insert, hookname)) { + console.log(hookname) + //@ts-ignore + this.addHook(hookname, async () => await insert[hookname]()) + } + } + addSingleton(key: string, insert: object) { + if(typeof insert !== 'object') { + throw Error("Inserted object must be an object"); + } + if(!this.__singletons.has(key)){ + this.registerHooks('init', insert) + this.registerHooks('dispose', insert) + this.__singletons.set(key, insert); + return true; + } + return false; + } + + addWiredSingleton(key: string, fn: (c: Container) => object) { + const insert = fn(this); + return this.addSingleton(key, insert); + } + + async disposeAll() { + await this.executeHooks('dispose'); + this.hooks.delete('dispose'); + } + + isReady() { return this.finished_init; } + hasKey(key: string) { return this.__singletons.has(key); } + get(key: PropertyKey) : T|undefined { return this.__singletons.get(key); } + + async ready() { + await this.executeHooks('init'); + this.hooks.delete('init'); + this.finished_init = true; + } + + deps>(): T { + return Object.fromEntries(this.__singletons) as T + } + + async executeHooks(name: string) { + const hookFunctions = this.hooks.get(name) || []; + for (const hookFunction of hookFunctions) { + await hookFunction(); + } + } +} + diff --git a/packages/ioc/tsconfig.json b/packages/ioc/tsconfig.json index 1208ce0..d4ca08f 100644 --- a/packages/ioc/tsconfig.json +++ b/packages/ioc/tsconfig.json @@ -25,8 +25,9 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ESNext", /* Specify what module code is generated. */ "rootDir": "./src", /* Specify the root folder within your source files. */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ diff --git a/packages/localizer/index.ts b/packages/localizer/index.ts index fc36d69..f2bc42e 100644 --- a/packages/localizer/index.ts +++ b/packages/localizer/index.ts @@ -87,16 +87,19 @@ export const localize = (root?: string) => //@ts-ignore CommandInitPlugin(({ module, deps }) => { if(module.type === CommandType.Slash || module.type === CommandType.Both) { - deps['@sern/logger'].info({ message: "Localizing "+ module.name }); - const resolvedLocalization= 'command/'+(root??module.name); - Reflect.set(module, 'name_localizations', deps.localizer.translationsFor(resolvedLocalization+".name")); - Reflect.set(module, 'description_localizations', deps.localizer.translationsFor(resolvedLocalization+'.description')); + const { localizer, '@sern/logger':log } = deps + log?.info({ message: "Localizing "+ module.name }); + const resolvedRoot = 'command/'+(root??module.name); + dfsApplyLocalization(module.options ?? [], deps, [resolvedRoot]); //@ts-ignore - const newOpts = module.options ?? []; - //@ts-ignore - dfsApplyLocalization(newOpts, deps, [resolvedLocalization]); - return controller.next(); + return controller.next({ + locals: { + name_localizations: localizer.translationsFor(resolvedRoot+".name"), + description_localizations: localizer.translationsFor(resolvedRoot+'.description') + } + }); } else { + //@ts-ignore return controller.stop("Cannot localize this type of module " + module.name); } }) diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index ff5523d..8281bcc 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -72,12 +72,12 @@ export class Publisher implements Init { .map(module => { return { //@ts-ignore - [PUBLISH]: module[PUBLISH], + guildIds: module.publish.guildIds ?? [], toJSON() { const applicationType = intoApplicationType(module.type); const { default_member_permissions, integration_types=['Guild'],//@ts-ignore - contexts } = module[PUBLISH] ?? {}; + contexts } = module.publish ?? {}; return { name: module.name, type: applicationType, //@ts-ignore we know description is at least empty str or filled @@ -93,16 +93,16 @@ export class Publisher implements Init { }), contexts, //@ts-ignore - name_localizations: module.name_localizations, + name_localizations: module.locals.name_localizations, //@ts-ignore - description_localizations: module.description_localizations + description_localizations: module.locals.description_localizations } } } }) const [globalCommands, guildedCommands] = modules.reduce( ([globals, guilded], module) => { - const isPublishableGlobally = !module[PUBLISH] || !Array.isArray(module[PUBLISH].guildIds); + const isPublishableGlobally = !module.guildIds || module.guildIds.length === 0; if (isPublishableGlobally) { return [[module, ...globals], guilded]; } @@ -205,21 +205,24 @@ export const publishConfig = (config: ValidPublishOptions) => { return CommandInitPlugin(({ module, absPath }) => { if((module.type & PUBLISHABLE) === 0) { //@ts-ignore - return controller.stop("Cannot publish this module"); + return controller.stop("Cannot publish this module; Not of type Both,Slash,CtxUsr,CtxMsg."); } let _config=config if(typeof _config === 'function') { _config = _config(absPath, module); } const { contexts, defaultMemberPermissions, integrationTypes } = _config - //adding extra configuration - Reflect.set(module, PUBLISH, { - guildIds: _config.guildIds, - default_member_permissions: serializePermissions(defaultMemberPermissions), - integration_types: integrationTypes, - contexts - }) - return controller.next(); + //@ts-ignore + return controller.next({ + locals: { + publish: { + guildIds: _config.guildIds, + default_member_permissions: serializePermissions(defaultMemberPermissions), + integration_types: integrationTypes, + contexts + } + } + }); }) } From dbab08407fa917f5a87ec0a61d4f98c9f2cefc50 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:37:11 -0500 Subject: [PATCH 12/14] localizer and publisher better practices --- packages/localizer/index.ts | 8 ++++---- packages/publisher/index.mdx | 5 ++++- packages/publisher/index.ts | 26 ++++++++++++-------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/localizer/index.ts b/packages/localizer/index.ts index f2bc42e..689990b 100644 --- a/packages/localizer/index.ts +++ b/packages/localizer/index.ts @@ -93,10 +93,10 @@ export const localize = (root?: string) => dfsApplyLocalization(module.options ?? [], deps, [resolvedRoot]); //@ts-ignore return controller.next({ - locals: { - name_localizations: localizer.translationsFor(resolvedRoot+".name"), - description_localizations: localizer.translationsFor(resolvedRoot+'.description') - } + locals: { + nloc: localizer.translationsFor(resolvedRoot+".name"), + dloc: localizer.translationsFor(resolvedRoot+'.description') + } }); } else { //@ts-ignore diff --git a/packages/publisher/index.mdx b/packages/publisher/index.mdx index 2d89e25..d1cd15b 100644 --- a/packages/publisher/index.mdx +++ b/packages/publisher/index.mdx @@ -19,7 +19,7 @@ NODE_ENV= **Initializing the Publisher** ```ts import { makeDependencies } from '@sern/handler'; -import { Publisher } from '@sern/localizer'; +import { Publisher } from '@sern/publisher'; await makeDependencies(({ add }) => { add('publisher', new Publisher()); @@ -52,6 +52,9 @@ type ValidPublishOptions = | PublishConfig | (absPath: string, module: CommandModule) => PublishConfig ``` +:::tip +These types are exported under @sern/publisher +::: ### Example: command published with integrationTypes diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 8281bcc..7a779bd 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -25,7 +25,7 @@ const makeDescription = (type: number, desc: string) => { return desc; }; -const serializePermissions = (perms: unknown) => { +const serializePerms = (perms: unknown) => { if(typeof perms === 'bigint' || typeof perms === 'number') { return perms.toString(); } @@ -85,17 +85,12 @@ export class Publisher implements Init { //@ts-ignore shutup options: optionsTransformer(module?.options), default_member_permissions, - integration_types: integration_types.map( - (s: string) => { - if(s === "Guild") return "0"; - else if (s == "User") return "1"; - else throw Error("IntegrationType is not one of Guild or User"); - }), + integration_types, contexts, //@ts-ignore - name_localizations: module.locals.name_localizations, + name_localizations: module.locals.nloc, //@ts-ignore - description_localizations: module.locals.description_localizations + description_localizations: module.locals.dloc } } } @@ -195,6 +190,9 @@ export type ValidPublishOptions = | PublishConfig | ((absPath: string, module: CommandModule) => PublishConfig) +const IntegrationType = { + Guild: '0', User: '1' +} /** * the publishConfig plugin. * If your commandModule requires extra properties such as publishing for certain guilds, you would @@ -211,15 +209,15 @@ export const publishConfig = (config: ValidPublishOptions) => { if(typeof _config === 'function') { _config = _config(absPath, module); } - const { contexts, defaultMemberPermissions, integrationTypes } = _config + const { contexts, defaultMemberPermissions, integrationTypes:integration_types, guildIds } = _config //@ts-ignore return controller.next({ locals: { publish: { - guildIds: _config.guildIds, - default_member_permissions: serializePermissions(defaultMemberPermissions), - integration_types: integrationTypes, - contexts + guildIds, + contexts, + integration_types: integration_types?.map(i => Reflect.get(IntegrationType, i)), + default_member_permissions: serializePerms(defaultMemberPermissions), } } }); From ea77ae4488f08b5f62c70f225e116251793818be Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Fri, 14 Jun 2024 20:05:49 -0500 Subject: [PATCH 13/14] publisher --- packages/ioc/package.json | 12 ++++----- packages/localizer/index.ts | 14 +++++------ packages/publisher/index.ts | 44 ++++++++++++++++----------------- packages/publisher/package.json | 2 +- yarn.lock | 2 +- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/packages/ioc/package.json b/packages/ioc/package.json index 9d63bc6..bb5ad56 100644 --- a/packages/ioc/package.json +++ b/packages/ioc/package.json @@ -3,15 +3,15 @@ "version": "1.0.3", "description": "Dependency Injection system", "main": "dist/index.js", - "module": "./dist/index.js", + "module": "./dist/index.js", "exports": { - "." : { - "import": "./dist/index.js", - "require": "./dist/index.js" + ".": { + "import": "./dist/index.js", + "require": "./dist/index.js" }, "./global": { - "import": "./dist/global.js", - "require": "./dist/global.js" + "import": "./dist/global.js", + "require": "./dist/global.js" } }, "scripts": { diff --git a/packages/localizer/index.ts b/packages/localizer/index.ts index 689990b..a08347e 100644 --- a/packages/localizer/index.ts +++ b/packages/localizer/index.ts @@ -67,6 +67,7 @@ export const local = (i: string, local: string) => { /** * An init plugin to add localization fields to a command module. * Your localization configuration should look like, + * sets nloc and dloc on locals field of module. * @param root {string} If you have conflicting command names, you may configure the root of the name. (= command/{root}) * Below is es-ES.json (spanish) * ```json @@ -88,16 +89,13 @@ export const localize = (root?: string) => CommandInitPlugin(({ module, deps }) => { if(module.type === CommandType.Slash || module.type === CommandType.Both) { const { localizer, '@sern/logger':log } = deps - log?.info({ message: "Localizing "+ module.name }); const resolvedRoot = 'command/'+(root??module.name); - dfsApplyLocalization(module.options ?? [], deps, [resolvedRoot]); + log?.info({ message: "Localizing "+ resolvedRoot }); //@ts-ignore - return controller.next({ - locals: { - nloc: localizer.translationsFor(resolvedRoot+".name"), - dloc: localizer.translationsFor(resolvedRoot+'.description') - } - }); + dfsApplyLocalization(module.options ?? [], deps, [resolvedRoot]); + Reflect.set(module.locals, 'nloc', localizer.translationsFor(resolvedRoot+".name")) + Reflect.set(module.locals, 'dloc', localizer.translationsFor(resolvedRoot+'.description')) + return controller.next(); } else { //@ts-ignore return controller.stop("Cannot localize this type of module " + module.name); diff --git a/packages/publisher/index.ts b/packages/publisher/index.ts index 7a779bd..1d3e135 100644 --- a/packages/publisher/index.ts +++ b/packages/publisher/index.ts @@ -39,8 +39,6 @@ const serializePerms = (perms: unknown) => { const BASE_URL = new URL('https://discord.com/api/v10/applications/'); const PUBLISHABLE = 0b1110; -const PUBLISH = Symbol.for('@sern/publish') - export class Publisher implements Init { constructor(private modules: Map, @@ -63,21 +61,26 @@ export class Publisher implements Init { throw e; } const GLOBAL_URL = new URL(`${appid}/commands`, BASE_URL); - + interface LocalPublish { + guildIds?: string[] + default_member_permissions: string, + integration_types: string[], + contexts: number[] + } const listener = async () => { this.logger.info({ message: 'publishing modules' }); const modules = Array.from(this.modules.values()) .filter(module => (module.type & PUBLISHABLE) != 0) .map(module => { + const publish = module.locals.publish as LocalPublish || {} return { - //@ts-ignore - guildIds: module.publish.guildIds ?? [], + guildIds: publish?.guildIds ?? [], toJSON() { const applicationType = intoApplicationType(module.type); const { default_member_permissions, - integration_types=['Guild'],//@ts-ignore - contexts } = module.publish ?? {}; + integration_types, + contexts } = publish; return { name: module.name, type: applicationType, //@ts-ignore we know description is at least empty str or filled @@ -85,8 +88,7 @@ export class Publisher implements Init { //@ts-ignore shutup options: optionsTransformer(module?.options), default_member_permissions, - integration_types, - contexts, + integration_types, contexts, //@ts-ignore name_localizations: module.locals.nloc, //@ts-ignore @@ -96,6 +98,7 @@ export class Publisher implements Init { } }) const [globalCommands, guildedCommands] = modules.reduce( + //technically these aren't sern/handler modules. ([globals, guilded], module) => { const isPublishableGlobally = !module.guildIds || module.guildIds.length === 0; if (isPublishableGlobally) { @@ -119,7 +122,7 @@ export class Publisher implements Init { const guildIdMap: Map = new Map(); const responsesMap = new Map(); guildedCommands.forEach((entry) => { - const guildIds: string[] = entry[PUBLISH].guildIds ?? []; + const guildIds: string[] = entry.guildIds ?? []; if (guildIds) { guildIds.forEach((guildId) => { if (guildIdMap.has(guildId)) { @@ -197,6 +200,7 @@ const IntegrationType = { * the publishConfig plugin. * If your commandModule requires extra properties such as publishing for certain guilds, you would * put those options in there. + * sets 'publish' on locals field for modules. * @param {ValidPublishOptions} config options to configure how this module is published */ export const publishConfig = (config: ValidPublishOptions) => { @@ -207,20 +211,16 @@ export const publishConfig = (config: ValidPublishOptions) => { } let _config=config if(typeof _config === 'function') { - _config = _config(absPath, module); + _config = _config(absPath, module as CommandModule); } const { contexts, defaultMemberPermissions, integrationTypes:integration_types, guildIds } = _config - //@ts-ignore - return controller.next({ - locals: { - publish: { - guildIds, - contexts, - integration_types: integration_types?.map(i => Reflect.get(IntegrationType, i)), - default_member_permissions: serializePerms(defaultMemberPermissions), - } - } - }); + Reflect.set(module.locals, 'publish', { + guildIds, + contexts, + integration_types: integration_types?.map(i => Reflect.get(IntegrationType, i)), + default_member_permissions: serializePerms(defaultMemberPermissions), + }) + return controller.next(); }) } diff --git a/packages/publisher/package.json b/packages/publisher/package.json index 16bcb3f..340de9d 100644 --- a/packages/publisher/package.json +++ b/packages/publisher/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "tsc", "watch": "tsc --watch", - "test": "vitest --run" + "test": "exit 0" }, "devDependencies": { "@sern/handler": "^3.3.0", diff --git a/yarn.lock b/yarn.lock index 7f15779..7f51199 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2448,7 +2448,7 @@ __metadata: "typescript@patch:typescript@^5.0.0#~builtin": version: 5.4.5 - resolution: "typescript@patch:typescript@npm%3A5.4.5#~builtin::version=5.4.5&hash=f3b441" + resolution: "typescript@patch:typescript@npm%3A5.4.5#~builtin::version=5.4.5&hash=14eedb" bin: tsc: bin/tsc tsserver: bin/tsserver From 58bc2ee52c3676c64c04930169d560c42da1d731 Mon Sep 17 00:00:00 2001 From: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com> Date: Fri, 14 Jun 2024 20:07:12 -0500 Subject: [PATCH 14/14] remove uneeded container class --- packages/ioc/src/container.ts | 75 ----------------------------------- 1 file changed, 75 deletions(-) delete mode 100644 packages/ioc/src/container.ts diff --git a/packages/ioc/src/container.ts b/packages/ioc/src/container.ts deleted file mode 100644 index 61b5393..0000000 --- a/packages/ioc/src/container.ts +++ /dev/null @@ -1,75 +0,0 @@ - -function hasCallableMethod(obj: object, name: PropertyKey) { - // object will always be defined - // @ts-ignore - return typeof obj[name] == 'function'; -} -/** - * A Depedency injection container capable of adding singletons, firing hooks, and managing IOC within an application - */ -export class Container { - private __singletons = new Map(); - private hooks= new Map(); - private finished_init = false; - constructor(options: { autowire: boolean; path?: string }) { - if(options.autowire) { /* noop */ } - } - - addHook(name: string, callback: Function) { - if (!this.hooks.has(name)) { - this.hooks.set(name, []); - } - this.hooks.get(name)!.push(callback); - } - private registerHooks(hookname: string, insert: object) { - if(hasCallableMethod(insert, hookname)) { - console.log(hookname) - //@ts-ignore - this.addHook(hookname, async () => await insert[hookname]()) - } - } - addSingleton(key: string, insert: object) { - if(typeof insert !== 'object') { - throw Error("Inserted object must be an object"); - } - if(!this.__singletons.has(key)){ - this.registerHooks('init', insert) - this.registerHooks('dispose', insert) - this.__singletons.set(key, insert); - return true; - } - return false; - } - - addWiredSingleton(key: string, fn: (c: Container) => object) { - const insert = fn(this); - return this.addSingleton(key, insert); - } - - async disposeAll() { - await this.executeHooks('dispose'); - this.hooks.delete('dispose'); - } - - isReady() { return this.finished_init; } - hasKey(key: string) { return this.__singletons.has(key); } - get(key: PropertyKey) : T|undefined { return this.__singletons.get(key); } - - async ready() { - await this.executeHooks('init'); - this.hooks.delete('init'); - this.finished_init = true; - } - - deps>(): T { - return Object.fromEntries(this.__singletons) as T - } - - async executeHooks(name: string) { - const hookFunctions = this.hooks.get(name) || []; - for (const hookFunction of hookFunctions) { - await hookFunction(); - } - } -} -