command modules do not depend on anything but itself

This commit is contained in:
Jacob Nguyen
2024-04-28 15:50:29 -05:00
parent 6228f53244
commit 599a02c9df
7 changed files with 88 additions and 45 deletions

View File

@@ -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>;

View File

@@ -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;
}

View File

@@ -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');

View File

@@ -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,

View File

@@ -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;

View File

@@ -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");
})
})

View File

@@ -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,
});