@eggjs/utils
Version:
Utils for all egg projects
168 lines (146 loc) • 4.83 kB
text/typescript
import { debuglog } from 'node:util';
import path from 'node:path';
import assert from 'node:assert';
import os from 'node:os';
import { stat, mkdir, writeFile, realpath } from 'node:fs/promises';
import { importModule } from './import.js';
const debug = debuglog('@eggjs/utils/plugin');
const tmpDir = os.tmpdir();
function noop() {}
const logger = {
debug: noop,
info: noop,
warn: noop,
error: noop,
};
export interface LoaderOptions {
framework: string;
baseDir: string;
env?: string;
}
export interface Plugin {
name: string;
version?: string;
enable: boolean;
implicitEnable: boolean;
path: string;
dependencies: string[];
optionalDependencies: string[];
env: string[];
from: string;
}
/**
* @see https://github.com/eggjs/egg-core/blob/2920f6eade07959d25f5c4f96b154d3fbae877db/lib/loader/mixin/plugin.js#L203
*/
export async function getPlugins(options: LoaderOptions) {
const loader = await getLoader(options);
await loader.loadPlugin();
return loader.allPlugins;
}
interface Unit {
type: 'plugin' | 'framework' | 'app';
path: string;
}
/**
* @see https://github.com/eggjs/egg-core/blob/2920f6eade07959d25f5c4f96b154d3fbae877db/lib/loader/egg_loader.js#L348
*/
export async function getLoadUnits(options: LoaderOptions) {
const loader = await getLoader(options);
await loader.loadPlugin();
return loader.getLoadUnits();
}
export async function getConfig(options: LoaderOptions) {
const loader = await getLoader(options);
await loader.loadPlugin();
await loader.loadConfig();
return loader.config;
}
async function exists(filepath: string) {
try {
await stat(filepath);
return true;
} catch {
return false;
}
}
interface IEggLoader {
loadPlugin(): Promise<void>;
loadConfig(): Promise<void>;
config: Record<string, any>;
getLoadUnits(): Unit[];
allPlugins: Record<string, Plugin>;
}
interface IEggLoaderOptions {
baseDir: string;
app: unknown;
logger: object;
EggCoreClass?: unknown;
}
type EggLoaderImplClass<T = IEggLoader> = new(options: IEggLoaderOptions) => T;
export async function getLoader(options: LoaderOptions) {
assert(options.framework, 'framework is required');
assert(await exists(options.framework), `${options.framework} should exist`);
if (!(options.baseDir && await exists(options.baseDir))) {
options.baseDir = path.join(tmpDir, 'egg_utils', `${Date.now()}`, 'tmp_app');
await mkdir(options.baseDir, { recursive: true });
await writeFile(path.join(options.baseDir, 'package.json'), JSON.stringify({
name: 'tmp_app',
}));
debug('[getLoader] create baseDir: %o', options.baseDir);
}
const { EggCore, EggLoader } = await findEggCore(options);
const mod = await importModule(options.framework);
const Application = mod.Application ?? mod.default?.Application;
assert(Application, `Application not export on ${options.framework}`);
if (options.env) {
process.env.EGG_SERVER_ENV = options.env;
}
return new EggLoader({
baseDir: options.baseDir,
logger,
app: Object.create(Application.prototype),
EggCoreClass: EggCore,
});
}
export async function findEggCore(options: LoaderOptions): Promise<{ EggCore?: object; EggLoader: EggLoaderImplClass }> {
const baseDirRealpath = await realpath(options.baseDir);
const frameworkRealpath = await realpath(options.framework);
const paths = [ frameworkRealpath, baseDirRealpath ];
// custom framework => egg => @eggjs/core
try {
const { EggCore, EggLoader } = await importModule('egg', { paths });
if (EggLoader) {
return { EggCore, EggLoader };
}
} catch (err: any) {
debug('[findEggCore] import "egg" from paths:%o error: %o', paths, err);
}
// egg-core 在 6.2.3 版本中更名为 @eggjs/core,为兼容老版本,支持同时查找两个包,优先使用新名字
const names = [ '@eggjs/core', 'egg-core' ];
for (const name of names) {
try {
const { EggCore, EggLoader } = await importModule(name, { paths });
if (EggLoader) {
return { EggCore, EggLoader };
}
} catch (err: any) {
debug('[findEggCore] import "%s" from paths:%o error: %o', name, paths, err);
}
try {
const { EggCore, EggLoader } = await importModule(name);
if (EggLoader) {
return { EggCore, EggLoader };
}
} catch (err: any) {
debug('[findEggCore] import "%s" error: %o', name, err);
}
let eggCorePath = path.join(options.baseDir, `node_modules/${name}`);
if (!(await exists(eggCorePath))) {
eggCorePath = path.join(options.framework, `node_modules/${name}`);
}
if (await exists(eggCorePath)) {
return await importModule(eggCorePath);
}
}
assert(false, `Can't find ${names.join(' or ')} from ${options.baseDir} and ${options.framework}`);
}