Repository: artusjs/core (ts)

Issue:
[RFC] 为 Plugin/Framework 提供入口模块用于元信息声明
在此前的实践中，我们使用 `meta.yaml` 声明插件的元信息，用于依赖的计算和扫描参数的指定，但实践中有如下场景造成使用不便：

- `meta.yaml`/`expection.yaml` 在 tsc 打包过程中属于非标文件，需要单独处理
- 通常 `meta.yaml` 作为元信息文件，被放置在包根目录下，代价是需要对包目录进行全部扫描，对于同时存在 src/lib 的场景（如 monorepo）使用不便
- `meta.yaml` 动态能力不足，包内 Class 只能通过 Loader 机制导出/注入，对于部分不适合直接由 IoC 初始化的类，需要依靠 Lifecycle 实现注入，开发体验不佳

综上，考虑修正 Spec，引入入口文件机制替代 `meta.yaml`，大致方案如下：

- 在所有以包为单位导出的插件中，要求通过 `package.json` 的 `main` 字段或者 `export['.']` 字段导出根类（Class）
- 对于并通过装饰器修饰传递元信息参数，且视为 Injectable 类，由 IoC Contianer 托管
- 类内可提供 `init` 方法用于基础类初始化
- 后续可增加其他钩子用于干预插件的扫描/加载过程
- 该文件在 Manifest 中默认被 Scanner 列入所在 unit 的 config 之后，module 之前，由 `plugin` loader 处理之，替代目前的 `plugin-meta` loader

伪代码（以 Plugin 为例，Framework 同理，仅参数和装饰器有所不同）如下：

```json
// package.json
{
  "name": "@artus/plugin-hello",
  "main": "lib/index.js", // Will bundled by tsc
  "types": "lib/index.d.ts"
}
```

```plain
./plugin
├── exceptions.yaml
├── lib
│   ├── clazz.d.ts
│   ├── clazz.js
│   ├── index.d.ts
│   └── index.js
├── package.json
└── src
    ├── clazz.ts
    └── index.ts
```

```ts
// src/index.ts
import path from 'path';
import {
  DefinePlugin,
  PluginInterface,
  Container,
  Inject
} from '@artus/core';
import MyExceptionList from './exceptions.json';

@DefinePlugin({
  name: 'hello',
  dependencies: [
    { name: 'world' }, // Default optional is false
    { name: 'optional-world', optional: true },
  ],
  rootDir: __dirname, // Default
  configDir: './config', // Default
  exclude: [
    './test'
  ],
  expections: [
    path.resolve(__dirname, './exceptions.yaml'), // Default
    MyExceptionList, // Support imported js object
    path.resolve(__dirname, '../exceptions.yaml'), // Compatible with defination in root dir
  ]
})
export default class HelloPlugin implements PluginInterface {
  @Inject()
  container: Container;

  async init() {
    console.log('Hello world');
    this.container.set({
      id: 'hello',
      value: 'world'
    });
  }
}
```

```json
// manifest.json
{
  "default": {
    "items": [
      {
        "path": "node_modules/@artus/plugin-hello/lib/lifecycle",
        "extname": ".js",
        "filename": "lifecycle.js",
        "loader": "lifecycle-hook-unit",
        "source": "plugin",
        "unitName": "hello"
      },
      // ... Other Lifecycle Hook Unit
      {
        "path": "config/config.default",
        "extname": ".js",
        "filename": "config.default.js",
        "loader": "config",
        "source": "app"
      },
      {
        "path": "node_modules/@artus/plugin-world/lib/index", // Dependencies of HelloPlugin
        "extname": ".js",
        "filename": "index.js",
        "loader": "plugin",
        "source": "plugin",
        "unitName": "plugin-world"
      },
      {
        "path": "node_modules/@artus/plugin-hello/lib/index",
        "extname": ".js",
        "filename": "index.js",
        "loader": "plugin",
        "source": "plugin",
        "unitName": "plugin-hello"
      },
      {
        "path": "node_modules/@artus/plugin-world/lib/clazz", // Dependencies of HelloPlugin
        "extname": ".js",
        "filename": "clazz.js",
        "loader": "module",
        "source": "plugin",
        "unitName": "plugin-world"
      },
      {
        "path": "node_modules/@artus/plugin-hello/lib/clazz",
        "extname": ".js",
        "filename": "clazz.js",
        "loader": "module",
        "source": "plugin",
        "unitName": "plugin-hello"
      }
      // ... Other Plugin and App module
    ],
    "relative": true
  }
}
```

已知的 Breaking 体现在：

- `meta.(yaml|json|yml)` 将移除
- 扫描入口转移为 main 文件所在文件夹，对包内其他目录不再扫描
  - 可增加元信息补充 `extraScanDir`
- 初始化的 init 钩子执行时机比 `didLoad` 早，虽更有利，但具有一定传染性
   - 如 hello 的 init 依赖 world，那 world 的初始化逻辑也需改为 init 形式，如使用 didLoad 则会挂掉
   - 初步评估也合理（？）

@atian25 @hyj1991  
@DuanPengfei @JerrysShan @Beace @wengeezhang 
@gxcsoccer 

Pull request description:
feat: remove all yaml meta file, change them to JSON
resolve #137 

Patch:
diff --git a/exception.json b/exception.json
new file mode 100644
index 0000000..3312866
--- /dev/null
+++ b/exception.json
@@ -0,0 +1,13 @@
+{
+  "ARTUS:GLOBAL_TEST": {
+      "desc": "全局测试错误，仅用于单元测试",
+      "detailUrl": "https://github.com/artusjs/spec"
+  },
+  "ARTUS:GLOBAL_TEST_I18N": {
+      "desc": {
+          "zh": "全局测试错误，仅用于单元测试",
+          "en": "This is a test exception, only valid in unit-test"
+      },
+      "detailUrl": "https://github.com/artusjs/spec"
+  }
+}
\ No newline at end of file
diff --git a/exception.yaml b/exception.yaml
deleted file mode 100644
index 75e4c21..0000000
--- a/exception.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-ARTUS:GLOBAL_TEST:
-  desc: 全局测试错误，仅用于单元测试
-  detailUrl: https://github.com/artusjs/spec
-ARTUS:GLOBAL_TEST_I18N:
-  desc:
-    zh: 全局测试错误，仅用于单元测试
-    en: This is a test exception, only valid in unit-test
-  detailUrl: https://github.com/artusjs/spec
\ No newline at end of file
diff --git a/package.json b/package.json
index c6ef4fd..e1565b4 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,6 @@
     "@artus/injection": "^0.3.1",
     "@artus/pipeline": "^0.2.2",
     "deepmerge": "^4.2.2",
-    "js-yaml": "^4.1.0",
     "minimatch": "^5.0.1"
   },
   "ci": {
diff --git a/src/constant.ts b/src/constant.ts
index 6e31ca8..367f797 100644
--- a/src/constant.ts
+++ b/src/constant.ts
@@ -46,9 +46,9 @@ export const DEFAULT_EXCLUDES = [
 export const FRAMEWORK_PATTERN = 'framework.*';
 export const PLUGIN_CONFIG_PATTERN = 'plugin.*';
 export const CONFIG_PATTERN = 'config.*';
-export const PLUGIN_META = ['meta.json', 'meta.yaml', 'meta.yml'];
 export const PACKAGE_JSON = 'package.json';
-export const EXCEPTION_FILE = 'exception.yaml';
+export const PLUGIN_META_FILENAME = 'meta.json';
+export const EXCEPTION_FILENAME = 'exception.json';
 
 export const DEFAULT_LOADER_LIST_WITH_ORDER = [
   'exception',
diff --git a/src/loader/impl/exception.ts b/src/loader/impl/exception.ts
index cf399c9..b10eadc 100644
--- a/src/loader/impl/exception.ts
+++ b/src/loader/impl/exception.ts
@@ -4,7 +4,7 @@ import { ManifestItem, Loader, LoaderFindOptions } from '../types';
 import { ExceptionItem } from '../../exception/types';
 import { ExceptionHandler } from '../../exception';
 import { loadMetaFile } from '../../utils/load_meta_file';
-import { EXCEPTION_FILE } from '../../constant';
+import { EXCEPTION_FILENAME } from '../../constant';
 import { isMatch } from '../../utils';
 
 @DefineLoader('exception')
@@ -16,13 +16,13 @@ class ExceptionLoader implements Loader {
   }
 
   static async is(opts: LoaderFindOptions) {
-    return isMatch(opts.filename, EXCEPTION_FILE);
+    return isMatch(opts.filename, EXCEPTION_FILENAME);
   }
 
   async load(item: ManifestItem) {
     const exceptionHandler = this.container.get(ExceptionHandler);
     try {
-      const codeMap: Record<string, ExceptionItem> = await loadMetaFile<Record<string, ExceptionItem>>(item);
+      const codeMap: Record<string, ExceptionItem> = await loadMetaFile<Record<string, ExceptionItem>>(item.path);
       for (const [errCode, exceptionItem] of Object.entries(codeMap)) {
         exceptionHandler.registerCode(errCode, exceptionItem);
       }
diff --git a/src/loader/impl/plugin_meta.ts b/src/loader/impl/plugin_meta.ts
index 9002281..00bb3d4 100644
--- a/src/loader/impl/plugin_meta.ts
+++ b/src/loader/impl/plugin_meta.ts
@@ -3,7 +3,8 @@ import { DefineLoader } from '../decorator';
 import { ManifestItem, Loader, LoaderFindOptions } from '../types';
 import { loadMetaFile } from '../../utils/load_meta_file';
 import { PluginMetadata } from '../../plugin/types';
-import { PLUGIN_META } from '../../constant';
+import { PLUGIN_META_FILENAME } from '../../constant';
+import { isMatch } from '../../utils';
 
 @DefineLoader('plugin-meta')
 class PluginMetaLoader implements Loader {
@@ -14,11 +15,11 @@ class PluginMetaLoader implements Loader {
   }
 
   static async is(opts: LoaderFindOptions): Promise<boolean> {
-    return PLUGIN_META.includes(opts.filename);
+    return isMatch(opts.filename, PLUGIN_META_FILENAME);
   }
 
   async load(item: ManifestItem) {
-    const pluginMeta: PluginMetadata = await loadMetaFile<PluginMetadata>(item);
+    const pluginMeta: PluginMetadata = await loadMetaFile<PluginMetadata>(item.path);
     this.container.set({
       id: `pluginMeta_${pluginMeta.name}`,
       value: pluginMeta,
diff --git a/src/plugin/base.ts b/src/plugin/base.ts
index be5469c..6d53a91 100644
--- a/src/plugin/base.ts
+++ b/src/plugin/base.ts
@@ -4,7 +4,7 @@ type PluginMap = Map<string, BasePlugin>;
 
 export class BasePlugin implements Plugin {
   static getPath(packageName: string): string {
-    return path.resolve(require.resolve(`${packageName}/package.json`), '..');
+    return path.resolve(require.resolve(packageName), '..');
   }
 
   public name: string;
diff --git a/src/plugin/impl.ts b/src/plugin/impl.ts
index 3c92387..a6257fe 100644
--- a/src/plugin/impl.ts
+++ b/src/plugin/impl.ts
@@ -2,6 +2,7 @@ import path from 'path';
 import { BasePlugin } from './base';
 import { loadMetaFile } from '../utils/load_meta_file';
 import { exisis } from '../utils/fs';
+import { PLUGIN_META_FILENAME } from '../constant';
 
 export class ArtusPlugin extends BasePlugin {
   async init() {
@@ -22,34 +23,15 @@ export class ArtusPlugin extends BasePlugin {
     if (!await exisis(this.importPath)) {
       throw new Error(`load plugin <${this.name}> import path ${this.importPath} is not exists.`);
     }
-
-    let find = false;
-    const fileNameList = [
-      'meta.yaml',
-      'meta.yml',
-      'meta.json',
-    ];
-    for (const fileName of fileNameList) {
-      const metaFilePath = path.resolve(this.importPath, fileName);
-      try {
-        if (!await exisis(metaFilePath)) {
-          continue;
-        }
-        this.metadata = await loadMetaFile({
-          path: metaFilePath,
-          extname: path.extname(metaFilePath),
-          filename: fileName,
-        });
-        this.metaFilePath = metaFilePath;
-        find = true;
-        break;
-      } catch (e) {
-        throw new Error(`load plugin <${this.name}> failed, err: ${e}`);
+    const metaFilePath = path.resolve(this.importPath, PLUGIN_META_FILENAME);
+    try {
+      if (!await exisis(metaFilePath)) {
+        throw new Error(`load plugin <${this.name}> import path ${this.importPath} can't find meta file.`);
       }
-    }
-
-    if (!find) {
-      throw new Error(`load plugin <${this.name}> import path ${this.importPath} can't find meta file.`);
+      this.metadata = await loadMetaFile(metaFilePath);
+      this.metaFilePath = metaFilePath;
+    } catch (e) {
+      throw new Error(`load plugin <${this.name}> failed, err: ${e}`);
     }
   }
 }
diff --git a/src/scanner/scan.ts b/src/scanner/scan.ts
index 5c4d4e1..b166816 100644
--- a/src/scanner/scan.ts
+++ b/src/scanner/scan.ts
@@ -34,7 +34,7 @@ export class Scanner {
       loaderListGenerator: (defaultLoaderList: string[]) => defaultLoaderList,
       ...options,
       exclude: DEFAULT_EXCLUDES.concat(options.exclude ?? []),
-      extensions: [...new Set(this.moduleExtensions.concat(options.extensions ?? [], ['.yaml']))],
+      extensions: [...new Set(this.moduleExtensions.concat(options.extensions ?? []))],
     };
   }
 
diff --git a/src/scanner/utils.ts b/src/scanner/utils.ts
index 1049868..44818cf 100644
--- a/src/scanner/utils.ts
+++ b/src/scanner/utils.ts
@@ -6,7 +6,7 @@ import { Container } from '@artus/injection';
 import {
   ArtusInjectEnum,
   DEFAULT_LOADER,
-  PLUGIN_META,
+  PLUGIN_META_FILENAME,
 } from '../constant';
 import { LoaderFactory, ManifestItem } from '../loader';
 import { WalkOptions } from './types';
@@ -38,8 +38,7 @@ export class ScanUtils {
       const itemStat = await fs.stat(realPath);
       if (itemStat.isDirectory()) {
         // ignore plugin dir
-        // TODO:  怎么判断是否是插件文件夹
-        if (this.exist(realPath, PLUGIN_META)) {
+        if (this.exist(realPath, [PLUGIN_META_FILENAME])) {
           continue;
         }
         await ScanUtils.walk(realPath, options);
diff --git a/src/utils/load_meta_file.ts b/src/utils/load_meta_file.ts
index f77f411..f11c974 100644
--- a/src/utils/load_meta_file.ts
+++ b/src/utils/load_meta_file.ts
@@ -1,33 +1,9 @@
-import yaml from 'js-yaml';
-import { readFile } from 'fs/promises';
-import { ManifestItem } from '../loader/types';
+import compatibleRequire from './compatible_require';
 
-type ParserFunction = <T = Record<string, any>>(content: string) => T;
-
-const YamlParser: ParserFunction = <T = Record<string, any>>(content: string) => {
-  return yaml.load(content, {
-    json: true,
-  }) as T;
-};
-const JsonParser: ParserFunction = <T = Record<string, any>>(content: string) => {
-  return JSON.parse(content) as T;
-};
-
-export const loadMetaFile = async <T = Record<string, any>>(item: ManifestItem): Promise<T> => {
-  let parserFunc: ParserFunction;
-  if (item.extname === '.yaml' || item.extname === '.yml') {
-    parserFunc = YamlParser;
-  } else if (item.extname === '.json') {
-    parserFunc = JsonParser;
-  } else {
-    throw new Error(`[Artus-Loader] Unsupported file extension: ${item.extname} in ${item.path}`);
-  }
-  const content = await readFile(item.path, {
-    encoding: 'utf-8',
-  });
-  if (!content) {
-    throw new Error(`[Artus-Loader] File content is empty in ${item.path}.`);
+export const loadMetaFile = async <T = Record<string, any>>(path: string): Promise<T> => {
+  const metaObject = await compatibleRequire(path);
+  if (!metaObject || typeof metaObject !== 'object') {
+    throw new Error(`[loadMetaFile] ${path} is not a valid json file.`);
   }
-  const resultMap = parserFunc<T>(content);
-  return resultMap;
+  return metaObject;
 };


Test patch:
diff --git a/test/fixtures/app_koa_with_ts/src/exception.json b/test/fixtures/app_koa_with_ts/src/exception.json
new file mode 100644
index 0000000..3312866
--- /dev/null
+++ b/test/fixtures/app_koa_with_ts/src/exception.json
@@ -0,0 +1,13 @@
+{
+  "ARTUS:GLOBAL_TEST": {
+      "desc": "全局测试错误，仅用于单元测试",
+      "detailUrl": "https://github.com/artusjs/spec"
+  },
+  "ARTUS:GLOBAL_TEST_I18N": {
+      "desc": {
+          "zh": "全局测试错误，仅用于单元测试",
+          "en": "This is a test exception, only valid in unit-test"
+      },
+      "detailUrl": "https://github.com/artusjs/spec"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/app_koa_with_ts/src/exception.yaml b/test/fixtures/app_koa_with_ts/src/exception.yaml
deleted file mode 100644
index 7059258..0000000
--- a/test/fixtures/app_koa_with_ts/src/exception.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-ARTUS:GLOBAL_TEST:
-  desc: 全局测试错误，仅用于单元测试
-  detailUrl: https://github.com/artusjs/spec
-ARTUS:GLOBAL_TEST_I18N:
-  desc:
-    zh: 全局测试错误，仅用于单元测试
-    en: This is a test exception, only valid in unit-test
-  detailUrl: https://github.com/artusjs/spec
diff --git a/test/fixtures/app_koa_with_ts/src/mysql_plugin/meta.json b/test/fixtures/app_koa_with_ts/src/mysql_plugin/meta.json
new file mode 100644
index 0000000..7b58b02
--- /dev/null
+++ b/test/fixtures/app_koa_with_ts/src/mysql_plugin/meta.json
@@ -0,0 +1,3 @@
+{
+  "name": "mysql"
+}
\ No newline at end of file
diff --git a/test/fixtures/app_koa_with_ts/src/mysql_plugin/meta.yaml b/test/fixtures/app_koa_with_ts/src/mysql_plugin/meta.yaml
deleted file mode 100644
index 2fff827..0000000
--- a/test/fixtures/app_koa_with_ts/src/mysql_plugin/meta.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-name: mysql
diff --git a/test/fixtures/app_koa_with_ts/src/redis_plugin/meta.json b/test/fixtures/app_koa_with_ts/src/redis_plugin/meta.json
new file mode 100644
index 0000000..14ef928
--- /dev/null
+++ b/test/fixtures/app_koa_with_ts/src/redis_plugin/meta.json
@@ -0,0 +1,7 @@
+{
+  "name": "redis",
+  "exclude": [
+    "not_to_be_scanned_dir",
+    "not_to_be_scanned_file.ts"
+  ]
+}
\ No newline at end of file
diff --git a/test/fixtures/app_koa_with_ts/src/redis_plugin/meta.yaml b/test/fixtures/app_koa_with_ts/src/redis_plugin/meta.yaml
deleted file mode 100644
index 78bdbac..0000000
--- a/test/fixtures/app_koa_with_ts/src/redis_plugin/meta.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-name: redis
-exclude:
-  - not_to_be_scanned_dir
-  - not_to_be_scanned_file.ts
diff --git a/test/fixtures/app_koa_with_ts/src/test_duplicate_plugin/meta.json b/test/fixtures/app_koa_with_ts/src/test_duplicate_plugin/meta.json
new file mode 100644
index 0000000..7035cbc
--- /dev/null
+++ b/test/fixtures/app_koa_with_ts/src/test_duplicate_plugin/meta.json
@@ -0,0 +1,3 @@
+{
+    "name": "testDuplicate"
+}
\ No newline at end of file
diff --git a/test/fixtures/app_koa_with_ts/src/test_duplicate_plugin/meta.yaml b/test/fixtures/app_koa_with_ts/src/test_duplicate_plugin/meta.yaml
deleted file mode 100644
index d039e8d..0000000
--- a/test/fixtures/app_koa_with_ts/src/test_duplicate_plugin/meta.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-name: testDuplicate
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_base/meta.json b/test/fixtures/artus_application/src/plugins/artus_plugin_base/meta.json
new file mode 100644
index 0000000..d211bc0
--- /dev/null
+++ b/test/fixtures/artus_application/src/plugins/artus_plugin_base/meta.json
@@ -0,0 +1,3 @@
+{
+  "name": "base"
+}
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_base/meta.yml b/test/fixtures/artus_application/src/plugins/artus_plugin_base/meta.yml
deleted file mode 100644
index 4ac7b94..0000000
--- a/test/fixtures/artus_application/src/plugins/artus_plugin_base/meta.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-name: "base"
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_ob/meta.json b/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_ob/meta.json
new file mode 100644
index 0000000..7daab0d
--- /dev/null
+++ b/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_ob/meta.json
@@ -0,0 +1,9 @@
+{
+  "name": "mysql",
+  "configDir": "src/custom_config",
+  "dependencies": [
+      {
+          "name": "base"
+      }
+  ]
+}
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_ob/meta.yml b/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_ob/meta.yml
deleted file mode 100644
index 151499d..0000000
--- a/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_ob/meta.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-name: "mysql"
-configDir: src/custom_config
-dependencies:
-  - name: "base"
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_rds/meta.json b/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_rds/meta.json
new file mode 100644
index 0000000..7b58b02
--- /dev/null
+++ b/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_rds/meta.json
@@ -0,0 +1,3 @@
+{
+  "name": "mysql"
+}
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_rds/meta.yml b/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_rds/meta.yml
deleted file mode 100644
index 45c4f58..0000000
--- a/test/fixtures/artus_application/src/plugins/artus_plugin_mysql_rds/meta.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-name: "mysql"
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_redis/meta.json b/test/fixtures/artus_application/src/plugins/artus_plugin_redis/meta.json
new file mode 100644
index 0000000..fc83022
--- /dev/null
+++ b/test/fixtures/artus_application/src/plugins/artus_plugin_redis/meta.json
@@ -0,0 +1,3 @@
+{
+    "name": "redis"
+}
\ No newline at end of file
diff --git a/test/fixtures/artus_application/src/plugins/artus_plugin_redis/meta.yml b/test/fixtures/artus_application/src/plugins/artus_plugin_redis/meta.yml
deleted file mode 100644
index d3d7052..0000000
--- a/test/fixtures/artus_application/src/plugins/artus_plugin_redis/meta.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-name: "redis"
\ No newline at end of file
diff --git a/test/fixtures/exception_with_ts_yaml/bootstrap.ts b/test/fixtures/exception_with_ts_yaml/bootstrap.ts
index 57c9c98..694bc78 100644
--- a/test/fixtures/exception_with_ts_yaml/bootstrap.ts
+++ b/test/fixtures/exception_with_ts_yaml/bootstrap.ts
@@ -14,16 +14,16 @@ async function main() {
         source: 'app',
       },
       {
-        path: path.resolve(__dirname, '../../../exception.yaml'),
-        extname: '.yaml',
-        filename: 'exception.yaml',
+        path: path.resolve(__dirname, '../../../exception.json'),
+        extname: '.json',
+        filename: 'exception.json',
         loader: 'exception',
         source: 'app',
       },
       {
-        path: path.resolve(__dirname, './exception.yaml'),
-        extname: '.yaml',
-        filename: 'exception.yaml',
+        path: path.resolve(__dirname, './exception.json'),
+        extname: '.json',
+        filename: 'exception.json',
         loader: 'exception',
         source: 'app',
       },
diff --git a/test/fixtures/exception_with_ts_yaml/exception.json b/test/fixtures/exception_with_ts_yaml/exception.json
new file mode 100644
index 0000000..e65c6ea
--- /dev/null
+++ b/test/fixtures/exception_with_ts_yaml/exception.json
@@ -0,0 +1,6 @@
+{
+  "APP:TEST_ERROR": {
+      "desc": "这是一个测试用的错误",
+      "detailUrl": "https://github.com/artusjs"
+  }
+}
\ No newline at end of file
diff --git a/test/fixtures/exception_with_ts_yaml/exception.yaml b/test/fixtures/exception_with_ts_yaml/exception.yaml
deleted file mode 100644
index c0869fa..0000000
--- a/test/fixtures/exception_with_ts_yaml/exception.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-APP:TEST_ERROR:
-  desc: 这是一个测试用的错误
-  detailUrl: https://github.com/artusjs
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_a/meta.json b/test/fixtures/plugins/plugin_a/meta.json
new file mode 100644
index 0000000..c46e6ed
--- /dev/null
+++ b/test/fixtures/plugins/plugin_a/meta.json
@@ -0,0 +1,12 @@
+{
+    "name": "plugin-a",
+    "dependencies": [
+        {
+            "name": "plugin-b"
+        },
+        {
+            "name": "plugin-c",
+            "optional": true
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_a/meta.yaml b/test/fixtures/plugins/plugin_a/meta.yaml
deleted file mode 100644
index 7058d13..0000000
--- a/test/fixtures/plugins/plugin_a/meta.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-name: "plugin-a"
-dependencies:
-  - name: "plugin-b"
-  - name: "plugin-c"
-    optional: true
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_b/meta.json b/test/fixtures/plugins/plugin_b/meta.json
new file mode 100644
index 0000000..1bf45bc
--- /dev/null
+++ b/test/fixtures/plugins/plugin_b/meta.json
@@ -0,0 +1,8 @@
+{
+    "name": "plugin-b",
+    "dependencies": [
+        {
+            "name": "plugin-c"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_b/meta.yaml b/test/fixtures/plugins/plugin_b/meta.yaml
deleted file mode 100644
index 687afb7..0000000
--- a/test/fixtures/plugins/plugin_b/meta.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-name: "plugin-b"
-dependencies:
-  - name: "plugin-c"
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_c/meta.json b/test/fixtures/plugins/plugin_c/meta.json
new file mode 100644
index 0000000..761feda
--- /dev/null
+++ b/test/fixtures/plugins/plugin_c/meta.json
@@ -0,0 +1,3 @@
+{
+  "name": "plugin-c"
+}
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_c/meta.yaml b/test/fixtures/plugins/plugin_c/meta.yaml
deleted file mode 100644
index 9fd3090..0000000
--- a/test/fixtures/plugins/plugin_c/meta.yaml
+++ /dev/null
@@ -1,1 +0,0 @@
-name: "plugin-c"
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_d/meta.json b/test/fixtures/plugins/plugin_d/meta.json
new file mode 100644
index 0000000..00741ac
--- /dev/null
+++ b/test/fixtures/plugins/plugin_d/meta.json
@@ -0,0 +1,9 @@
+{
+  "name": "plugin-d",
+  "dependencies": [
+      {
+          "name": "plugin-e",
+          "optional": true
+      }
+  ]
+}
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_d/meta.yaml b/test/fixtures/plugins/plugin_d/meta.yaml
deleted file mode 100644
index 8d10c84..0000000
--- a/test/fixtures/plugins/plugin_d/meta.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-name: "plugin-d"
-dependencies:
-  - name: "plugin-e"
-    optional: true
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_wrong_a/meta.json b/test/fixtures/plugins/plugin_wrong_a/meta.json
new file mode 100644
index 0000000..a1c84ec
--- /dev/null
+++ b/test/fixtures/plugins/plugin_wrong_a/meta.json
@@ -0,0 +1,8 @@
+{
+    "name": "plugin-wrong-a",
+    "dependencies": [
+        {
+            "name": "plugin-wrong-b"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_wrong_a/meta.yaml b/test/fixtures/plugins/plugin_wrong_a/meta.yaml
deleted file mode 100644
index 2e6c198..0000000
--- a/test/fixtures/plugins/plugin_wrong_a/meta.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-name: "plugin-wrong-a"
-dependencies:
-  - name: "plugin-wrong-b"
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_wrong_b/meta.json b/test/fixtures/plugins/plugin_wrong_b/meta.json
new file mode 100644
index 0000000..8cbe4a2
--- /dev/null
+++ b/test/fixtures/plugins/plugin_wrong_b/meta.json
@@ -0,0 +1,8 @@
+{
+    "name": "plugin-wrong-b",
+    "dependencies": [
+        {
+            "name": "plugin-wrong-a"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/test/fixtures/plugins/plugin_wrong_b/meta.yaml b/test/fixtures/plugins/plugin_wrong_b/meta.yaml
deleted file mode 100644
index a8a9138..0000000
--- a/test/fixtures/plugins/plugin_wrong_b/meta.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-name: "plugin-wrong-b"
-dependencies:
-  - name: "plugin-wrong-a"
\ No newline at end of file