diff --git a/core/common-util/package.json b/core/common-util/package.json index 1b87d817..2baa1059 100644 --- a/core/common-util/package.json +++ b/core/common-util/package.json @@ -37,7 +37,8 @@ }, "dependencies": { "globby": "^11.1.0", - "js-yaml": "^3.14.0" + "js-yaml": "^3.14.0", + "extend2": "^1.0.0" }, "publishConfig": { "access": "public" diff --git a/core/common-util/src/ModuleConfig.ts b/core/common-util/src/ModuleConfig.ts index f8a7cc4a..eba68e7c 100644 --- a/core/common-util/src/ModuleConfig.ts +++ b/core/common-util/src/ModuleConfig.ts @@ -4,8 +4,10 @@ import fs, { promises as fsPromise } from 'fs'; import path from 'path'; import globby from 'globby'; import { FSUtil } from './FSUtil'; +import extend from 'extend2'; export interface ModuleReference { + name: string; path: string; } @@ -45,11 +47,17 @@ const DEFAULT_READ_MODULE_REF_OPTS = { }; export class ModuleConfigUtil { - public static moduleYamlPath(modulePath: string): string { + public static moduleYamlPath(modulePath: string, env?: string): string { + if (env) { + return path.join(modulePath, `module.${env}.yml`); + } return path.join(modulePath, 'module.yml'); } - public static moduleJsonPath(modulePath: string): string { + public static moduleJsonPath(modulePath: string, env?: string): string { + if (env) { + return path.join(modulePath, `module.${env}.json`); + } return path.join(modulePath, 'module.json'); } @@ -75,12 +83,16 @@ export class ModuleConfigUtil { // path.posix for windows keep path as foo/package.json const pkgJson = path.posix.join(moduleReferenceConfig.package, 'package.json'); const file = require.resolve(pkgJson, options); + const modulePath = path.dirname(file); moduleReference = { - path: path.dirname(file), + path: modulePath, + name: ModuleConfigUtil.readModuleNameSync(modulePath), }; } else if (ModuleReferenceConfigHelp.isInlineModuleReference(moduleReferenceConfig)) { + const modulePath = path.join(configDir, moduleReferenceConfig.path); moduleReference = { - path: path.join(configDir, moduleReferenceConfig.path), + path: modulePath, + name: ModuleConfigUtil.readModuleNameSync(modulePath), }; } else { throw new Error('unknown type of module reference config: ' + JSON.stringify(moduleReferenceConfig)); @@ -124,13 +136,15 @@ export class ModuleConfigUtil { } moduleDirSet.add(moduleDir); + let name: string; try { - this.readModuleNameSync(moduleDir); + name = this.readModuleNameSync(moduleDir); } catch (_) { continue; } ref.push({ path: moduleDir, + name, }); } const moduleReferences = this.readModuleFromNodeModules(baseDir); @@ -143,6 +157,7 @@ export class ModuleConfigUtil { }); ref.push({ path: moduleReference.path, + name: moduleReference.name, }); } return ref; @@ -169,11 +184,11 @@ export class ModuleConfigUtil { const absolutePkgPath = path.dirname(packageJsonPath); const realPkgPath = fs.realpathSync(absolutePkgPath); try { - if (this.readModuleNameSync(realPkgPath)) { - ref.push({ - path: realPkgPath, - }); - } + const name = this.readModuleNameSync(realPkgPath); + ref.push({ + path: realPkgPath, + name, + }); } catch (_) { continue; } @@ -189,33 +204,44 @@ export class ModuleConfigUtil { return path.join(baseDir, 'config', moduleDir); } + private static getModuleName(pkg: any) { + assert(pkg.eggModule && pkg.eggModule.name, 'eggModule.name not found in package.json'); + return pkg.eggModule.name; + } + public static async readModuleName(baseDir: string, moduleDir: string): Promise { moduleDir = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir); const pkgContent = await fsPromise.readFile(path.join(moduleDir, 'package.json'), 'utf8'); const pkg = JSON.parse(pkgContent); - assert(pkg.eggModule && pkg.eggModule.name, 'eggModule.name not found in package.json'); - return pkg.eggModule.name; + return ModuleConfigUtil.getModuleName(pkg); } public static readModuleNameSync(moduleDir: string, baseDir?: string): string { moduleDir = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir); const pkgContent = fs.readFileSync(path.join(moduleDir, 'package.json'), 'utf8'); const pkg = JSON.parse(pkgContent); - assert(pkg.eggModule && pkg.eggModule.name, 'eggModule.name not found in package.json'); - return pkg.eggModule.name; + return ModuleConfigUtil.getModuleName(pkg); } - public static async loadModuleConfig(moduleDir: string, baseDir?: string): Promise { + public static async loadModuleConfig(moduleDir: string, baseDir?: string, env?: string): Promise { moduleDir = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir); - const yamlConfig = await ModuleConfigUtil.loadModuleYaml(moduleDir); - if (yamlConfig) { - return yamlConfig; + let defaultConfig = await ModuleConfigUtil.loadModuleYaml(moduleDir); + if (!defaultConfig) { + defaultConfig = await ModuleConfigUtil.loadModuleJson(moduleDir); } - return await ModuleConfigUtil.loadModuleJson(moduleDir); + let envConfig: ModuleConfig | undefined; + if (env) { + envConfig = await ModuleConfigUtil.loadModuleYaml(moduleDir, env); + if (!envConfig) { + envConfig = await ModuleConfigUtil.loadModuleJson(moduleDir, env); + } + } + extend(true, defaultConfig, envConfig); + return defaultConfig; } - private static async loadModuleJson(moduleDir: string): Promise { - const moduleJsonPath = ModuleConfigUtil.moduleJsonPath(moduleDir); + private static async loadModuleJson(moduleDir: string, env?: string): Promise { + const moduleJsonPath = ModuleConfigUtil.moduleJsonPath(moduleDir, env); const moduleJsonPathExists = await FSUtil.fileExists(moduleJsonPath); if (!moduleJsonPathExists) { return; @@ -225,8 +251,8 @@ export class ModuleConfigUtil { return moduleJson.config; } - private static async loadModuleYaml(moduleDir: string): Promise { - const moduleYamlPath = ModuleConfigUtil.moduleYamlPath(moduleDir); + private static async loadModuleYaml(moduleDir: string, env?: string): Promise { + const moduleYamlPath = ModuleConfigUtil.moduleYamlPath(moduleDir, env); const moduleYamlPathExists = await FSUtil.fileExists(moduleYamlPath); if (!moduleYamlPathExists) { return; @@ -235,17 +261,25 @@ export class ModuleConfigUtil { return yaml.safeLoad(moduleYamlContent) as ModuleConfigUtil; } - public static loadModuleConfigSync(moduleDir: string, baseDir?: string): ModuleConfig | undefined { + public static loadModuleConfigSync(moduleDir: string, baseDir?: string, env?: string): ModuleConfig | undefined { moduleDir = ModuleConfigUtil.resolveModuleDir(moduleDir, baseDir); - const yamlConfig = ModuleConfigUtil.loadModuleYamlSync(moduleDir); - if (yamlConfig) { - return yamlConfig; + let defaultConfig = ModuleConfigUtil.loadModuleYamlSync(moduleDir); + if (!defaultConfig) { + defaultConfig = ModuleConfigUtil.loadModuleJsonSync(moduleDir); + } + let envConfig: ModuleConfig | undefined; + if (env) { + envConfig = ModuleConfigUtil.loadModuleYamlSync(moduleDir, env); + if (!envConfig) { + envConfig = ModuleConfigUtil.loadModuleJsonSync(moduleDir, env); + } } - return ModuleConfigUtil.loadModuleJsonSync(moduleDir); + extend(true, defaultConfig, envConfig); + return defaultConfig; } - private static loadModuleJsonSync(moduleDir: string): ModuleConfig | undefined { - const moduleJsonPath = ModuleConfigUtil.moduleJsonPath(moduleDir); + private static loadModuleJsonSync(moduleDir: string, env?: string): ModuleConfig | undefined { + const moduleJsonPath = ModuleConfigUtil.moduleJsonPath(moduleDir, env); const moduleJsonPathExists = fs.existsSync(moduleJsonPath); if (!moduleJsonPathExists) { return; @@ -255,8 +289,8 @@ export class ModuleConfigUtil { return moduleJson.config; } - private static loadModuleYamlSync(moduleDir: string): ModuleConfig | undefined { - const moduleYamlPath = ModuleConfigUtil.moduleYamlPath(moduleDir); + private static loadModuleYamlSync(moduleDir: string, env?: string): ModuleConfig | undefined { + const moduleYamlPath = ModuleConfigUtil.moduleYamlPath(moduleDir, env); const moduleYamlPathExists = fs.existsSync(moduleYamlPath); if (!moduleYamlPathExists) { return; diff --git a/core/common-util/test/ModuleConfig.test.ts b/core/common-util/test/ModuleConfig.test.ts index 48d33765..e407bf08 100644 --- a/core/common-util/test/ModuleConfig.test.ts +++ b/core/common-util/test/ModuleConfig.test.ts @@ -8,6 +8,11 @@ describe('test/ModuleConfig.test.ts', () => { const config = ModuleConfigUtil.loadModuleConfigSync(path.join(__dirname, './fixtures/modules/foo-yaml')); assert.deepStrictEqual(config, { mysql: { host: '127.0.0.1' } }); }); + + it('should load env', () => { + const config = ModuleConfigUtil.loadModuleConfigSync(path.join(__dirname, './fixtures/modules/dev-module-config'), undefined, 'dev'); + assert.deepStrictEqual(config, { mysql: { host: '127.0.0.1', port: 11306 } }); + }); }); describe('load module reference', () => { @@ -16,10 +21,10 @@ describe('test/ModuleConfig.test.ts', () => { const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-no-module-json'); const ref = ModuleConfigUtil.readModuleReference(fixturesPath); assert.deepStrictEqual(ref, [ - { path: path.join(fixturesPath, 'app/module-a') }, - { path: path.join(fixturesPath, 'app/module-b') }, - { path: path.join(fixturesPath, 'app/module-b/test/fixtures/module-e') }, - { path: path.join(fixturesPath, 'node_modules/module-c') }, + { path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' }, + { path: path.join(fixturesPath, 'app/module-b'), name: 'moduleB' }, + { path: path.join(fixturesPath, 'app/module-b/test/fixtures/module-e'), name: 'moduleE' }, + { path: path.join(fixturesPath, 'node_modules/module-c'), name: 'moduleC' }, ]); }); @@ -33,7 +38,7 @@ describe('test/ModuleConfig.test.ts', () => { const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-symlink'); const ref = ModuleConfigUtil.readModuleReference(fixturesPath); assert.deepStrictEqual(ref, [ - { path: path.join(fixturesPath, 'app/module-a') }, + { path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' }, ]); }); }); @@ -44,8 +49,8 @@ describe('test/ModuleConfig.test.ts', () => { const fixturesPath = path.join(__dirname, './fixtures/apps/app-with-module-json'); const ref = ModuleConfigUtil.readModuleReference(fixturesPath); assert.deepStrictEqual(ref, [ - { path: path.join(fixturesPath, 'app/module-a') }, - { path: path.join(fixturesPath, 'app/module-b') }, + { path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' }, + { path: path.join(fixturesPath, 'app/module-b'), name: 'moduleB' }, ]); }); }); @@ -57,7 +62,7 @@ describe('test/ModuleConfig.test.ts', () => { cwd: fixturesPath, }); assert.deepStrictEqual(ref, [ - { path: path.join(fixturesPath, 'node_modules/module-a') }, + { path: path.join(fixturesPath, 'node_modules/module-a'), name: 'moduleA' }, ]); }); }); @@ -71,7 +76,7 @@ describe('test/ModuleConfig.test.ts', () => { }; const ref = ModuleConfigUtil.readModuleReference(fixturesPath, readModuleOptions); assert.deepStrictEqual(ref, [ - { path: path.join(fixturesPath, 'app/module-a') }, + { path: path.join(fixturesPath, 'app/module-a'), name: 'moduleA' }, ]); }); }); @@ -90,6 +95,7 @@ describe('test/ModuleConfig.test.ts', () => { const ret = ModuleConfigUtil.readModuleFromNodeModules(dir); assert.deepStrictEqual(ret, [{ path: path.resolve(__dirname, './fixtures/monorepo/packages/d/node_modules/e'), + name: 'e', }]); }); @@ -98,6 +104,7 @@ describe('test/ModuleConfig.test.ts', () => { const ret = ModuleConfigUtil.readModuleFromNodeModules(dir); assert.deepStrictEqual(ret, [{ path: path.resolve(__dirname, './fixtures/monorepo/packages/a/node_modules/c'), + name: 'c', }]); }); @@ -106,6 +113,7 @@ describe('test/ModuleConfig.test.ts', () => { const ret = ModuleConfigUtil.readModuleFromNodeModules(dir); assert.deepStrictEqual(ret, [{ path: path.resolve(__dirname, './fixtures/monorepo/packages/a'), + name: 'a', }]); }); }); diff --git a/core/common-util/test/fixtures/modules/dev-module-config/module.dev.yml b/core/common-util/test/fixtures/modules/dev-module-config/module.dev.yml new file mode 100644 index 00000000..0f79ce2e --- /dev/null +++ b/core/common-util/test/fixtures/modules/dev-module-config/module.dev.yml @@ -0,0 +1,2 @@ +mysql: + port: 11306 diff --git a/core/common-util/test/fixtures/modules/dev-module-config/module.yml b/core/common-util/test/fixtures/modules/dev-module-config/module.yml new file mode 100644 index 00000000..9e55e708 --- /dev/null +++ b/core/common-util/test/fixtures/modules/dev-module-config/module.yml @@ -0,0 +1,2 @@ +mysql: + host: 127.0.0.1 diff --git a/plugin/config/app.ts b/plugin/config/app.ts index 88c214a4..6e27c539 100644 --- a/plugin/config/app.ts +++ b/plugin/config/app.ts @@ -15,13 +15,14 @@ export default class App { for (const reference of this.app.moduleReferences) { const absoluteRef = { path: ModuleConfigUtil.resolveModuleDir(reference.path, this.app.baseDir), + name: reference.name, }; const moduleName = ModuleConfigUtil.readModuleNameSync(absoluteRef.path); this.app.moduleConfigs[moduleName] = { name: moduleName, reference: absoluteRef, - config: ModuleConfigUtil.loadModuleConfigSync(absoluteRef.path) || {}, + config: ModuleConfigUtil.loadModuleConfigSync(absoluteRef.path, undefined, this.app.config.env) || {}, }; } } diff --git a/plugin/config/test/ReadModule.test.ts b/plugin/config/test/ReadModule.test.ts index 98c3a6e5..a62c21f6 100644 --- a/plugin/config/test/ReadModule.test.ts +++ b/plugin/config/test/ReadModule.test.ts @@ -32,6 +32,7 @@ describe('test/ReadModule.test.ts', () => { config: {}, name: 'moduleA', reference: { + name: 'moduleA', path: path.join(fixturesPath, 'app/module-a'), }, }, diff --git a/standalone/standalone/src/Runner.ts b/standalone/standalone/src/Runner.ts index 866d2693..de96ff8f 100644 --- a/standalone/standalone/src/Runner.ts +++ b/standalone/standalone/src/Runner.ts @@ -32,7 +32,7 @@ export interface RunnerOptions { * use inner object handlers instead */ innerObjects?: Record; - + env?: string; innerObjectHandlers?: Record; } @@ -40,6 +40,7 @@ export class Runner { readonly cwd: string; readonly moduleReferences: readonly ModuleReference[]; readonly moduleConfigs: Record; + readonly env?: string; private loadUnitLoader: EggModuleLoader; private runnerProto: EggPrototype; private configSourceEggPrototypeHook: ConfigSourceLoadUnitHook; @@ -57,6 +58,7 @@ export class Runner { constructor(cwd: string, options?: RunnerOptions) { this.cwd = cwd; + this.env = options?.env; this.moduleReferences = ModuleConfigUtil.readModuleReference(this.cwd); this.moduleConfigs = {}; this.innerObjects = { @@ -77,13 +79,14 @@ export class Runner { for (const reference of this.moduleReferences) { const absoluteRef = { path: ModuleConfigUtil.resolveModuleDir(reference.path, this.cwd), + name: reference.name, }; const moduleName = ModuleConfigUtil.readModuleNameSync(absoluteRef.path); this.moduleConfigs[moduleName] = { name: moduleName, reference: absoluteRef, - config: ModuleConfigUtil.loadModuleConfigSync(absoluteRef.path) || {}, + config: ModuleConfigUtil.loadModuleConfigSync(absoluteRef.path, undefined, this.env) || {}, }; } for (const moduleConfig of Object.values(this.moduleConfigs)) { diff --git a/standalone/standalone/test/fixtures/module-with-env-config/foo.ts b/standalone/standalone/test/fixtures/module-with-env-config/foo.ts new file mode 100644 index 00000000..79a1b7b3 --- /dev/null +++ b/standalone/standalone/test/fixtures/module-with-env-config/foo.ts @@ -0,0 +1,28 @@ +import { ContextProto, Inject, SingletonProto } from '@eggjs/tegg'; +import { Runner, MainRunner } from '@eggjs/tegg/standalone'; +import { ModuleConfigs } from '../../../src/ModuleConfigs'; + +@SingletonProto() +export class Hello { + hello() { + return 'hello!'; + } +} + +@ContextProto() +export class HelloContext { + hello() { + return 'hello from ctx'; + } +} + +@ContextProto() +@Runner() +export class Foo implements MainRunner { + @Inject() + moduleConfigs: ModuleConfigs; + + async main(): Promise { + return this.moduleConfigs.get('simple')!; + } +} diff --git a/standalone/standalone/test/fixtures/module-with-env-config/module.dev.yml b/standalone/standalone/test/fixtures/module-with-env-config/module.dev.yml new file mode 100644 index 00000000..0d817dc7 --- /dev/null +++ b/standalone/standalone/test/fixtures/module-with-env-config/module.dev.yml @@ -0,0 +1,3 @@ +features: + dynamic: + foo: 'foo' diff --git a/standalone/standalone/test/fixtures/module-with-env-config/module.yml b/standalone/standalone/test/fixtures/module-with-env-config/module.yml new file mode 100644 index 00000000..a0046c04 --- /dev/null +++ b/standalone/standalone/test/fixtures/module-with-env-config/module.yml @@ -0,0 +1,3 @@ +features: + dynamic: + foo: 'bar' diff --git a/standalone/standalone/test/fixtures/module-with-env-config/package.json b/standalone/standalone/test/fixtures/module-with-env-config/package.json new file mode 100644 index 00000000..1c4562cc --- /dev/null +++ b/standalone/standalone/test/fixtures/module-with-env-config/package.json @@ -0,0 +1,6 @@ +{ + "name": "simple", + "eggModule": { + "name": "simple" + } +} diff --git a/standalone/standalone/test/index.test.ts b/standalone/standalone/test/index.test.ts index 0e7d9588..fe8640be 100644 --- a/standalone/standalone/test/index.test.ts +++ b/standalone/standalone/test/index.test.ts @@ -54,6 +54,19 @@ describe('test/index.test.ts', () => { }, }); }); + + it('should work with env', async () => { + const config = await main(path.join(__dirname, './fixtures/module-with-env-config'), { + env: 'dev', + }); + assert.deepStrictEqual(config, { + features: { + dynamic: { + foo: 'foo', + }, + }, + }); + }); }); describe('@ConfigSource qualifier', () => {