From 62b1f972826f5a51901ff36e6bbd8f4a9d7a570f Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Fri, 3 Jan 2025 21:10:31 +0800 Subject: [PATCH] fix: export EggAppConfig type let plugin can override it (#286) ## Summary by CodeRabbit - **Type Safety Improvements** - Enhanced type definitions for application configuration - Centralized type management across modules - Introduced more precise type checking for configuration objects - **Code Structure** - Refactored type imports and exports - Updated case style handling with new enum - Simplified type management in loader components - **Testing** - Added type exposure tests - Updated test configurations to reflect new type structures --- src/egg.ts | 5 +- src/index.ts | 1 + src/loader/egg_loader.ts | 72 ++++++-------------- src/loader/file_loader.ts | 11 ++- src/types.ts | 56 +++++++++++++++ test/index.test.ts | 11 +++ test/loader/file_loader.test.ts | 10 +-- test/loader/mixin/load_controller.test.ts | 2 +- test/loader/mixin/load_custom_loader.test.ts | 8 ++- 9 files changed, 110 insertions(+), 66 deletions(-) create mode 100644 src/types.ts diff --git a/src/egg.ts b/src/egg.ts index 15cc62d0..ff37dbbd 100644 --- a/src/egg.ts +++ b/src/egg.ts @@ -18,6 +18,7 @@ import type { Fun } from './utils/index.js'; import { Lifecycle } from './lifecycle.js'; import { EggLoader } from './loader/egg_loader.js'; import utils from './utils/index.js'; +import { EggAppConfig } from './types.js'; const debug = debuglog('@eggjs/core/egg'); @@ -247,8 +248,8 @@ export class EggCore extends KoaApplication { * @member {Config} * @since 1.0.0 */ - get config() { - return this.loader ? this.loader.config : {}; + get config(): EggAppConfig { + return this.loader ? this.loader.config : {} as EggAppConfig; } /** diff --git a/src/index.ts b/src/index.ts index acc04916..ebe768ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,3 +10,4 @@ export * from './loader/file_loader.js'; export * from './loader/context_loader.js'; export * from './utils/sequencify.js'; export * from './utils/timing.js'; +export type * from './types.js'; diff --git a/src/loader/egg_loader.ts b/src/loader/egg_loader.ts index 5059b631..4d2b5c83 100644 --- a/src/loader/egg_loader.ts +++ b/src/loader/egg_loader.ts @@ -10,7 +10,7 @@ import { extend } from 'extend2'; import { Request, Response, Application, Context as KoaContext } from '@eggjs/koa'; import { pathMatching, type PathMatchingOptions } from 'egg-path-matching'; import { now, diff } from 'performance-ms'; -import { FULLPATH, FileLoader, FileLoaderOptions } from './file_loader.js'; +import { CaseStyle, FULLPATH, FileLoader, FileLoaderOptions } from './file_loader.js'; import { ContextLoader, ContextLoaderOptions } from './context_loader.js'; import utils, { Fun } from '../utils/index.js'; import sequencify from '../utils/sequencify.js'; @@ -19,6 +19,7 @@ import type { Context, EggCore, MiddlewareFunc, } from '../egg.js'; import type { BaseContextClass } from '../base_context_class.js'; +import type { EggAppConfig, EggAppInfo, EggPluginInfo } from '../types.js'; const debug = debuglog('@eggjs/core/loader/egg_loader'); @@ -29,44 +30,6 @@ const originalPrototypes: Record = { application: Application.prototype, }; -export interface EggAppInfo { - /** package.json */ - pkg: Record; - /** the application name from package.json */ - name: string; - /** current directory of application */ - baseDir: string; - /** equals to serverEnv */ - env: string; - /** equals to serverScope */ - scope: string; - /** home directory of the OS */ - HOME: string; - /** baseDir when local and unittest, HOME when other environment */ - root: string; -} - -export interface EggPluginInfo { - /** the plugin name, it can be used in `dep` */ - name: string; - /** the package name of plugin */ - package?: string; - version?: string; - /** whether enabled */ - enable: boolean; - implicitEnable?: boolean; - /** the directory of the plugin package */ - path?: string; - /** the dependent plugins, you can use the plugin name */ - dependencies: string[]; - /** the optional dependent plugins. */ - optionalDependencies: string[]; - dependents?: string[]; - /** specify the serverEnv that only enable the plugin in it */ - env: string[]; - /** the file plugin config in. */ - from: string; -} export interface EggLoaderOptions { /** server env */ @@ -845,7 +808,7 @@ export class EggLoader { /** start Config loader */ configMeta: Record; - config: Record; + config: EggAppConfig; /** * Load config/config.js @@ -859,7 +822,10 @@ export class EggLoader { this.timing.start('Load Config'); this.configMeta = {}; - const target: Record = {}; + const target: EggAppConfig = { + middleware: [], + coreMiddleware: [], + }; // Load Application config first const appConfig = await this.#preloadAppConfig(); @@ -1208,7 +1174,7 @@ export class EggLoader { const servicePaths = this.getLoadUnits().map(unit => path.join(unit.path, 'app/service')); options = { call: true, - caseStyle: 'lower', + caseStyle: CaseStyle.lower, fieldClass: 'serviceClasses', directory: servicePaths, ...options, @@ -1248,7 +1214,7 @@ export class EggLoader { opt = { call: false, override: true, - caseStyle: 'lower', + caseStyle: CaseStyle.lower, directory: middlewarePaths, ...opt, }; @@ -1323,7 +1289,7 @@ export class EggLoader { this.timing.start('Load Controller'); const controllerBase = path.join(this.options.baseDir, 'app/controller'); opt = { - caseStyle: 'lower', + caseStyle: CaseStyle.lower, directory: controllerBase, initializer: (obj, opt) => { // return class if it exports a function @@ -1403,7 +1369,7 @@ export class EggLoader { case 'ctx': { assert(!(property in this.app.context), `customLoader should not override ctx.${property}`); const options = { - caseStyle: 'lower', + caseStyle: CaseStyle.lower, fieldClass: `${property}Classes`, ...loaderConfig, directory, @@ -1414,7 +1380,7 @@ export class EggLoader { case 'app': { assert(!(property in this.app), `customLoader should not override app.${property}`); const options = { - caseStyle: 'lower', + caseStyle: CaseStyle.lower, initializer: (Clazz: unknown) => { return isClass(Clazz) ? new Clazz(this.app) : Clazz; }, @@ -1533,10 +1499,11 @@ export class EggLoader { * @param {Object} options - see {@link FileLoader} * @since 1.0.0 */ - async loadToApp(directory: string | string[], property: string | symbol, options?: FileLoaderOptions) { + async loadToApp(directory: string | string[], property: string | symbol, + options?: Omit) { const target = {}; Reflect.set(this.app, property, target); - options = { + const loadOptions: FileLoaderOptions = { ...options, directory: options?.directory ?? directory, target, @@ -1545,7 +1512,7 @@ export class EggLoader { const timingKey = `Load "${String(property)}" to Application`; this.timing.start(timingKey); - await new FileLoader(options).load(); + await new FileLoader(loadOptions).load(); this.timing.end(timingKey); } @@ -1556,8 +1523,9 @@ export class EggLoader { * @param {Object} options - see {@link ContextLoader} * @since 1.0.0 */ - async loadToContext(directory: string | string[], property: string | symbol, options?: ContextLoaderOptions) { - options = { + async loadToContext(directory: string | string[], property: string | symbol, + options?: Omit) { + const loadOptions: ContextLoaderOptions = { ...options, directory: options?.directory || directory, property, @@ -1566,7 +1534,7 @@ export class EggLoader { const timingKey = `Load "${String(property)}" to Context`; this.timing.start(timingKey); - await new ContextLoader(options).load(); + await new ContextLoader(loadOptions).load(); this.timing.end(timingKey); } diff --git a/src/loader/file_loader.ts b/src/loader/file_loader.ts index 99b05162..7d46f8f5 100644 --- a/src/loader/file_loader.ts +++ b/src/loader/file_loader.ts @@ -11,7 +11,12 @@ const debug = debuglog('@eggjs/core/file_loader'); export const FULLPATH = Symbol('EGG_LOADER_ITEM_FULLPATH'); export const EXPORTS = Symbol('EGG_LOADER_ITEM_EXPORTS'); -export type CaseStyle = 'camel' | 'lower' | 'upper'; +export enum CaseStyle { + camel = 'camel', + lower = 'lower', + upper = 'upper', +} + export type CaseStyleFunction = (filepath: string) => string[]; export type FileLoaderInitializer = (exports: unknown, options: { path: string; pathName: string }) => unknown; export type FileLoaderFilter = (exports: unknown) => boolean; @@ -79,7 +84,7 @@ export class FileLoader { assert(options.directory, 'options.directory is required'); assert(options.target, 'options.target is required'); this.options = { - caseStyle: 'camel', + caseStyle: CaseStyle.camel, call: true, override: false, ...options, @@ -88,7 +93,7 @@ export class FileLoader { // compatible old options _lowercaseFirst_ if (this.options.lowercaseFirst === true) { utils.deprecated('lowercaseFirst is deprecated, use caseStyle instead'); - this.options.caseStyle = 'lower'; + this.options.caseStyle = CaseStyle.lower; } } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..e9cccc83 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,56 @@ +export interface EggAppInfo { + /** package.json */ + pkg: Record; + /** the application name from package.json */ + name: string; + /** current directory of application */ + baseDir: string; + /** equals to serverEnv */ + env: string; + /** equals to serverScope */ + scope: string; + /** home directory of the OS */ + HOME: string; + /** baseDir when local and unittest, HOME when other environment */ + root: string; +} + +export interface EggPluginInfo { + /** the plugin name, it can be used in `dep` */ + name: string; + /** the package name of plugin */ + package?: string; + version?: string; + /** whether enabled */ + enable: boolean; + implicitEnable?: boolean; + /** the directory of the plugin package */ + path?: string; + /** the dependent plugins, you can use the plugin name */ + dependencies: string[]; + /** the optional dependent plugins. */ + optionalDependencies: string[]; + dependents?: string[]; + /** specify the serverEnv that only enable the plugin in it */ + env: string[]; + /** the file plugin config in. */ + from: string; +} + +export interface CustomLoaderConfigItem { + /** the directory of the custom loader */ + directory: string; + /** the inject object, it can be app or ctx */ + inject: string; + /** whether load unit files */ + loadunit?: boolean; +} + +export interface EggAppConfig extends Record { + coreMiddleware: string[]; + middleware: string[]; + customLoader?: Record; + controller?: { + supportParams?: boolean; + }; +} diff --git a/test/index.test.ts b/test/index.test.ts index 510a8596..1f62f630 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,5 +1,6 @@ import { strict as assert } from 'node:assert'; import * as EggCore from '../src/index.js'; +import type { EggAppConfig } from '../src/index.js'; describe('test/index.test.ts', () => { it('should expose properties', () => { @@ -10,6 +11,7 @@ describe('test/index.test.ts', () => { console.log(Object.keys(EggCore)); assert.deepEqual(Object.keys(EggCore), [ 'BaseContextClass', + 'CaseStyle', 'ClassLoader', 'Context', 'ContextLoader', @@ -31,4 +33,13 @@ describe('test/index.test.ts', () => { 'utils', ]); }); + + it('should expose types', () => { + const config = { + coreMiddleware: [], + middleware: [], + } as EggAppConfig; + assert(config.middleware); + assert(config.coreMiddleware); + }); }); diff --git a/test/loader/file_loader.test.ts b/test/loader/file_loader.test.ts index 75a11c25..0591b348 100644 --- a/test/loader/file_loader.test.ts +++ b/test/loader/file_loader.test.ts @@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert'; import path from 'node:path'; import { isClass } from 'is-type-of'; import yaml from 'js-yaml'; -import { FileLoader } from '../../src/loader/file_loader.js'; +import { FileLoader, CaseStyle } from '../../src/loader/file_loader.js'; import { getFilepath } from '../helper.js'; const dirBase = getFilepath('load_dirs'); @@ -275,7 +275,7 @@ describe('test/loader/file_loader.test.ts', () => { await new FileLoader({ directory: path.join(dirBase, 'camelize'), target, - caseStyle: 'upper', + caseStyle: CaseStyle.upper, }).load(); assert(target.FooBar1); @@ -289,7 +289,7 @@ describe('test/loader/file_loader.test.ts', () => { await new FileLoader({ directory: path.join(dirBase, 'camelize'), target, - caseStyle: 'camel', + caseStyle: CaseStyle.camel, }).load(); assert(target.fooBar1); @@ -303,7 +303,7 @@ describe('test/loader/file_loader.test.ts', () => { await new FileLoader({ directory: path.join(dirBase, 'camelize'), target, - caseStyle: 'lower', + caseStyle: CaseStyle.lower, }).load(); assert(target.fooBar1); @@ -349,7 +349,7 @@ describe('test/loader/file_loader.test.ts', () => { await new FileLoader({ directory: path.join(dirBase, 'camelize'), target, - caseStyle: 'upper', + caseStyle: CaseStyle.upper, lowercaseFirst: true, }).load(); diff --git a/test/loader/mixin/load_controller.test.ts b/test/loader/mixin/load_controller.test.ts index 69a9b432..88855474 100644 --- a/test/loader/mixin/load_controller.test.ts +++ b/test/loader/mixin/load_controller.test.ts @@ -379,7 +379,7 @@ describe('test/loader/mixin/load_controller.test.ts', () => { }); it('should support parameter', async () => { - assert.equal(app.config.controller.supportParams, true); + assert.equal(app.config.controller!.supportParams, true); const ctx = { app }; const args = [ 1, 2, 3 ]; let r = await app.controller.generatorFunction.call(ctx, ...args); diff --git a/test/loader/mixin/load_custom_loader.test.ts b/test/loader/mixin/load_custom_loader.test.ts index 8a50faad..c80d8ffd 100644 --- a/test/loader/mixin/load_custom_loader.test.ts +++ b/test/loader/mixin/load_custom_loader.test.ts @@ -68,7 +68,7 @@ describe('test/loader/mixin/load_custom_loader.test.ts', () => { custom: { }, }, - }; + } as any; await app.loader.loadCustomLoader(); throw new Error('should not run'); } catch (err: any) { @@ -88,7 +88,7 @@ describe('test/loader/mixin/load_custom_loader.test.ts', () => { inject: 'unknown', }, }, - }; + } as any; await app.loader.loadCustomLoader(); throw new Error('should not run'); } catch (err: any) { @@ -102,6 +102,8 @@ describe('test/loader/mixin/load_custom_loader.test.ts', () => { const app = createApp('custom-loader'); try { app.loader.config = { + coreMiddleware: [], + middleware: [], customLoader: { config: { directory: 'app/config', @@ -125,7 +127,7 @@ describe('test/loader/mixin/load_custom_loader.test.ts', () => { inject: 'ctx', }, }, - }; + } as any; await app.loader.loadCustomLoader(); throw new Error('should not run'); } catch (err: any) {