diff --git a/src/core/_internal.ts b/src/core/_internal.ts index 1b2f35c..1e8f203 100644 --- a/src/core/_internal.ts +++ b/src/core/_internal.ts @@ -1,10 +1,22 @@ +import type { Result } from 'ts-results-es' + export * as Id from './id'; export * from './operators'; 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 __Services from './structures/services'; export { useContainerRaw } from './ioc/base'; +export type _Module = { + meta: { + id: string, + absPath: string + } + name: string, + execute : Function + [key: PropertyKey]: unknown +} + +export type VoidResult = Result; diff --git a/src/core/functions.ts b/src/core/functions.ts index a03cdf5..39ff159 100644 --- a/src/core/functions.ts +++ b/src/core/functions.ts @@ -118,3 +118,11 @@ export function resultPayload (type: T, module?: Module, reason?: unknown) { return { type, module, reason } as Payload & { type : T }; } + +export function pipe(arg: unknown, firstFn: Function, ...fns: Function[]): T { + let result = firstFn(arg); + for (let fn of fns) { + result = fn(result); + } + return result; +} diff --git a/src/core/module-loading.ts b/src/core/module-loading.ts index 2fa3a7f..51a6d30 100644 --- a/src/core/module-loading.ts +++ b/src/core/module-loading.ts @@ -1,6 +1,6 @@ import { type Observable, from, mergeMap, ObservableInput } from 'rxjs'; import { readdir, stat } from 'fs/promises'; -import { basename, extname, join, resolve, parse, dirname } from 'path'; +import path from 'node:path'; import assert from 'assert'; import { createRequire } from 'node:module'; import type { ImportPayload, Wrapper } from '../types/core'; @@ -10,11 +10,18 @@ import type { Logging } from './contracts/logging'; export const parseCallsite = (fpath: string) => { - return parse(fpath.replace(/file:\\?/, "")).name; + const pathobj = + path.parse(fpath.replace(/file:\\?/, "") + .split(path.sep) + .join(path.posix.sep)) + return { + name: pathobj.name, + absPath : path.posix.format(pathobj) + } } -export const shouldHandle = (path: string, fpath: string) => { - const file_name = fpath+extname(path); - let newPath = join(dirname(path), file_name) +export const shouldHandle = (pth: string, fpath: string) => { + const file_name = fpath+path.extname(pth); + let newPath = path.join(path.dirname(pth), file_name) .replace(/file:\\?/, ""); return { exists: existsSync(newPath), path: 'file:///'+newPath }; @@ -41,7 +48,7 @@ export async function importModule(absPath: string) { let commandModule = fileModule.default; - assert(commandModule , `Found no export @ ${absPath}. Forgot to ignore with "!"? (!${basename(absPath)})?`); + assert(commandModule , `Found no export @ ${absPath}. Forgot to ignore with "!"? (!${path.basename(absPath)})?`); if ('default' in commandModule ) { commandModule = commandModule.default; } @@ -54,7 +61,7 @@ export async function defaultModuleLoader(absPath: string): Mo return { module, absPath }; } -export const fmtFileName = (fileName: string) => parse(fileName).name; +export const fmtFileName = (fileName: string) => path.parse(fileName).name; /** * a directory string is converted into a stream of modules. @@ -69,21 +76,21 @@ export function buildModuleStream( .pipe(mergeMap(defaultModuleLoader)); } -export const getFullPathTree = (dir: string) => readPaths(resolve(dir)); +export const getFullPathTree = (dir: string) => readPaths(path.resolve(dir)); -export const filename = (path: string) => fmtFileName(basename(path)); +export const filename = (p: string) => fmtFileName(path.basename(p)); +const validExtensions = ['.js', '.cjs', '.mts', '.mjs', '.cts', '.ts', '']; 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)); + return filename[0] === '!' || !validExtensions.includes(path.extname(filename)); }; async function deriveFileInfo(dir: string, file: string) { - const fullPath = join(dir, file); + const fullPath = path.join(dir, file); return { fullPath, fileStats: await stat(fullPath), - base: basename(file) }; + base: path.basename(file) }; } async function* readPaths(dir: string): AsyncGenerator { @@ -114,12 +121,12 @@ export function loadConfig(wrapper: Wrapper | 'file', log: Logging | undefined): return wrapper; } log?.info({ message: 'Experimental loading of sern.config.json'}); - const config = requir(resolve('sern.config.json')); + const config = requir(path.resolve('sern.config.json')); const makePath = (dir: PropertyKey) => config.language === 'typescript' - ? join('dist', config.paths[dir]!) - : join(config.paths[dir]!); + ? path.join('dist', config.paths[dir]!) + : path.join(config.paths[dir]!); log?.info({ message: 'Loading config: ' + JSON.stringify(config, null, 4) }); const commandsPath = makePath('commands'); diff --git a/src/core/modules.ts b/src/core/modules.ts index 7b0b992..0b3946e 100644 --- a/src/core/modules.ts +++ b/src/core/modules.ts @@ -7,45 +7,55 @@ import type { InputCommand, InputEvent, } from '../types/core-modules'; -import { partitionPlugins } from './_internal'; +import { _Module, partitionPlugins } from './_internal'; import type { Awaitable } from '../types/utility'; import callsites from 'callsites'; import * as Files from './module-loading' -import path, { basename } from 'path'; import * as Id from './id' /** * @since 1.0.0 The wrapper function to define command modules for sern * @param mod */ -export function commandModule(mod: InputCommand): CommandModule { +export function commandModule(mod: InputCommand): _Module { const [onEvent, plugins] = partitionPlugins(mod.plugins); - const initCallsite = callsites()[1].getFileName()?.replace(/file:\\?/, ""); + const initCallsite = callsites()[1].getFileName(); if(!initCallsite) throw Error("initCallsite is null"); - const filename = Files.parseCallsite(initCallsite); - mod.name ??= filename; - const id = Id.create(mod.name, mod.type) + + const { name, absPath } = Files.parseCallsite(initCallsite); + mod.name ??= name; + //@ts-ignore return { ...mod, - __id: id, + meta: { + id: Id.create(mod.name, mod.type), + absPath + }, onEvent, plugins, - } as unknown as CommandModule; + }; } /** * @since 1.0.0 * The wrapper function to define event modules for sern * @param mod */ -export function eventModule(mod: InputEvent): EventModule { +export function eventModule(mod: InputEvent): _Module { const [onEvent, plugins] = partitionPlugins(mod.plugins); const initCallsite = callsites()[1].getFileName(); - console.log(initCallsite?.replace(/file:\\?/, "")) - + console.log(initCallsite); + if(!initCallsite) throw Error("initCallsite is null"); + const { name, absPath } = Files.parseCallsite(initCallsite); + mod.name ??= name; + //@ts-ignore return { ...mod, + meta: { + id: Id.create(mod.name, mod.type), + absPath + }, plugins, onEvent, - } as EventModule; + }; } /** Create event modules from discord.js client events, diff --git a/src/types/core-plugin.ts b/src/types/core-plugin.ts index 8ece7c3..ae92c68 100644 --- a/src/types/core-plugin.ts +++ b/src/types/core-plugin.ts @@ -47,9 +47,9 @@ import type { UserContextMenuCommandInteraction, UserSelectMenuInteraction, } from 'discord.js'; +import { VoidResult } from '../core/_internal'; export type PluginResult = Awaitable; -export type VoidResult = Result; export interface InitArgs> { module: T; diff --git a/test/core/module-loading.test.ts b/test/core/module-loading.test.ts index b3906c1..59521d4 100644 --- a/test/core/module-loading.test.ts +++ b/test/core/module-loading.test.ts @@ -8,13 +8,20 @@ describe('module-loading', () => { const filename = Files.fmtFileName(name+'.'+extension); expect(filename).toBe(name) }) - it('should get the filename of the commandmodule (linux)', () => { + it('should get the filename of the commandmodule (linux, esm)', () => { const fname = "///home/pooba/Projects/sern/halibu/dist/commands/ping.js" - expect(Files.parseCallsite(fname)).toBe("ping") + const callsiteinfo = Files.parseCallsite(fname) + expect(callsiteinfo.name).toBe("ping") }) - it('should get the filename of the commandmodule (windows)', () => { - //const fname = "C:\\pooba\\Projects\\sern\\halibu\\dist\\commands\\ping.js" - //expect(Files.parseCallsite(fname)).toBe("ping") + it('should get the filename of the commandmodule (windows, cjs)', () => { + const fname = "C:\\pooba\\Projects\\sern\\halibu\\dist\\commands\\ping.js" + const callsiteinfo = Files.parseCallsite(fname) + expect(callsiteinfo.name).toEqual("ping"); + }) + it('should get filename of commandmodule (windows, esm)', () => { + const fname = "file:///C:\\pooba\\Projects\\sern\\halibu\\dist\\commands\\ping.js" + const callsiteinfo = Files.parseCallsite(fname) + expect(callsiteinfo.name).toEqual("ping"); }) }) diff --git a/test/core/services.test.ts b/test/core/services.test.ts index 4f532f3..cfd963a 100644 --- a/test/core/services.test.ts +++ b/test/core/services.test.ts @@ -5,6 +5,14 @@ import { faker } from '@faker-js/faker'; import { commandModule, CommandType } from '../../src'; import * as Id from '../../src/core/id'; import { CommandMeta } from '../../src/types/core-modules'; +function createRandomCommandModules() { + return commandModule({ + type: CommandType.Slash, + description: faker.string.alpha(), + name: faker.string.alpha({ length: { min: 5, max: 10 }}), + execute: vi.fn(), + }); +} describe('services', () => { //@ts-ignore let container: CoreContainer; @@ -20,15 +28,6 @@ describe('services', () => { consoleMock.mockReset(); }); it('module-store.ts', async () => { - function createRandomCommandModules() { - return commandModule({ - type: CommandType.Slash, - description: faker.string.alpha(), - name: faker.string.alpha({ length: { min: 5, max: 10 }}), - execute: () => {}, - }); - } - const modules = faker.helpers.multiple(createRandomCommandModules, { count: 40, });