mirror of
https://github.com/sern-handler/handler
synced 2026-06-24 08:42:17 +00:00
command modules do not depend on anything but itself
This commit is contained in:
@@ -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<void, void>;
|
||||
|
||||
@@ -118,3 +118,11 @@ export function resultPayload<T extends PayloadType>
|
||||
(type: T, module?: Module, reason?: unknown) {
|
||||
return { type, module, reason } as Payload & { type : T };
|
||||
}
|
||||
|
||||
export function pipe<T>(arg: unknown, firstFn: Function, ...fns: Function[]): T {
|
||||
let result = firstFn(arg);
|
||||
for (let fn of fns) {
|
||||
result = fn(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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<T>(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<T extends Module>(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<T extends Module>(
|
||||
.pipe(mergeMap(defaultModuleLoader<T>));
|
||||
}
|
||||
|
||||
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<string> {
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -47,9 +47,9 @@ import type {
|
||||
UserContextMenuCommandInteraction,
|
||||
UserSelectMenuInteraction,
|
||||
} from 'discord.js';
|
||||
import { VoidResult } from '../core/_internal';
|
||||
|
||||
export type PluginResult = Awaitable<VoidResult>;
|
||||
export type VoidResult = Result<void, void>;
|
||||
|
||||
export interface InitArgs<T extends Processed<Module>> {
|
||||
module: T;
|
||||
|
||||
@@ -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");
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -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<Dependencies>;
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user