@oroinc/oro-webpack-config-builder 5.1.0-alpha9 → 5.1.0-lts001

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/error-handler.js +95 -0
  3. package/loader/config-loader.js +1 -2
  4. package/loader/inject-loader/LICENSE.md +21 -0
  5. package/loader/inject-loader/README.md +54 -0
  6. package/loader/inject-loader/index.js +10 -0
  7. package/loader/inject-loader/injectify.js +66 -0
  8. package/loader/inject-loader/package.json +55 -0
  9. package/loader/inject-loader/wrapper_template.js +32 -0
  10. package/modules-config/layout-modules-config-loader.js +66 -2
  11. package/modules-config/modules-config-loader.js +52 -19
  12. package/oro-webpack-config.js +410 -299
  13. package/package.json +37 -38
  14. package/plugin/logs/after-webpack-logs-plugin.js +25 -0
  15. package/style/admin-style-loader.js +4 -3
  16. package/style/layout-style-loader.js +3 -3
  17. package/style/style-loader.js +52 -7
  18. package/theme-config-factory.js +45 -6
  19. package/utils.js +30 -0
  20. package/validation/assets-validator.js +104 -0
  21. package/validation/errors/assets-input-file-error.js +24 -0
  22. package/validation/errors/assets-schema-error.js +40 -0
  23. package/validation/errors/base-error.js +37 -0
  24. package/validation/errors/jsmodules-extra-modules-error.js +22 -0
  25. package/validation/errors/jsmodules-schema-error.js +40 -0
  26. package/validation/errors/styles-error.js +24 -0
  27. package/validation/index.js +36 -0
  28. package/validation/jsmodules-validator.js +53 -0
  29. package/validation/schema-validator.js +62 -0
  30. package/validation/schemas/assets-schema-full.js +22 -0
  31. package/validation/schemas/assets-schema.js +32 -0
  32. package/validation/schemas/jsmodules-schema-full.js +11 -0
  33. package/validation/schemas/jsmodules-schema.js +76 -0
  34. package/writer/configs-file-writer.js +1 -1
  35. package/writer/dynamic-imports-file-writer.js +3 -3
  36. package/writer/scss-entry-point-file-writer.js +1 -1
package/README.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  An integration of OroPlatform based applications with the Webpack.
4
4
 
5
- For more details see [the documentation](https://doc.oroinc.com/backend/bundles/platform/AssetBundle/).
5
+ For more details see [the documentation](https://doc.oroinc.com/bundles/platform/AssetBundle/).
@@ -0,0 +1,95 @@
1
+ const BaseError = require('./validation/errors/base-error');
2
+ const {isVerboseMode} = require('./utils');
3
+ const {red, yellow, green, bgRed} = require('colorette');
4
+
5
+ const emptyLine = length => new Array(length).fill(' ').join('');
6
+ const multiline = (color, msg) => {
7
+ msg = ` ${msg}`;
8
+ msg = msg + emptyLine(120 - msg.length);
9
+
10
+ return `\n${color(emptyLine(msg.length))}\n${color(msg)}\n${color(emptyLine(msg.length))}`;
11
+ };
12
+
13
+ class ErrorHandler {
14
+ constructor() {
15
+ this.failedThemes = [];
16
+
17
+ process.on('beforeExit', code => {
18
+ this.onProcess();
19
+ });
20
+ }
21
+
22
+ /**
23
+ * Show a message about failed themes
24
+ */
25
+ onProcess() {
26
+ if (this.failedThemes.length) {
27
+ let msg = '[ERROR] Assets build';
28
+
29
+ if (this.failedThemes.length === 1) {
30
+ msg = `${msg} for "${this.failedThemes[0]}" theme failed.`;
31
+ } else {
32
+ msg = `${msg} for "${this.failedThemes.join(', ')}" themes failed.`;
33
+ }
34
+ console.error(multiline(bgRed, msg));
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Executes when the Webpack Compiler will complete a compilation
41
+ * In case of empty Webpack Config this method will not run
42
+ *
43
+ * @param {Object} stats
44
+ */
45
+ onBuildComplete(stats) {
46
+ const failed = stats?.compilation?.errors.length !== 0;
47
+
48
+ if (failed) {
49
+ console.error(multiline(bgRed, '[ERROR] Assets build failed.'));
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @param {Array|string} errors
56
+ * @param {string} [failedTheme]
57
+ */
58
+ displayError(errors, failedTheme) {
59
+ if (typeof failedTheme === 'string') {
60
+ this.failedThemes.push(failedTheme);
61
+ }
62
+
63
+ if (typeof errors === 'string') {
64
+ errors = [errors];
65
+ }
66
+
67
+ const errorMessage = errors.map(err => {
68
+ const highlightPrefix = str => {
69
+ const prefix = str.substring(0, str.search(':'));
70
+ return red(prefix) + str.substring(str.search(':'));
71
+ };
72
+
73
+ if (err instanceof BaseError) {
74
+ err.message = `${err.message}\n${green(err.extra)}`;
75
+ }
76
+
77
+ let msg = err.message;
78
+
79
+ if (isVerboseMode()) {
80
+ msg = highlightPrefix(err.stack);
81
+ } else {
82
+ const command = yellow('php bin/console oro:assets:build --env=dev --verbose');
83
+ const info = `${green(`Run the command ${command} to see more information`)}`;
84
+
85
+ msg = highlightPrefix(`${err.name}: ${msg}\n${info}`);
86
+ }
87
+
88
+ return `${msg}\n`;
89
+ });
90
+
91
+ console.error(errorMessage.join('\n'));
92
+ }
93
+ }
94
+
95
+ module.exports = ErrorHandler;
@@ -1,9 +1,8 @@
1
1
  const path = require('path');
2
- const _loaderUtils = require('loader-utils');
3
2
 
4
3
  module.exports = function(source) {
5
4
  this.cacheable && this.cacheable();
6
- const {resolver, relativeTo = ''} = _loaderUtils.getOptions(this) || {};
5
+ const {resolver, relativeTo = ''} = this.getOptions() || {};
7
6
 
8
7
  if (typeof resolver !== 'function') {
9
8
  return source;
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Justin Morris
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,54 @@
1
+ <div align="center">
2
+ <h3>💉📦<br><br><code>inject-loader</code></h3>
3
+ <h4>A Webpack loader for injecting code into modules via their dependencies</h4>
4
+ <a href="https://travis-ci.org/plasticine/inject-loader"><img src="https://img.shields.io/travis/plasticine/inject-loader/master.svg?style=flat-square" alt="build status" /></a> <a href="https://www.npmjs.com/package/inject-loader"><img src="https://img.shields.io/npm/v/inject-loader.svg?style=flat-square" alt="npm version" /></a> <a href="https://www.npmjs.com/package/inject-loader"><img src="https://img.shields.io/npm/dm/inject-loader.svg?style=flat-square" alt="npm downloads" /></a>
5
+ </div>
6
+
7
+ ***
8
+
9
+ ### Why
10
+
11
+ This is particularly useful for writing tests where mocking things inside your module-under-test is sometimes necessary before execution.
12
+
13
+ `inject-loader` was inspired by, and builds upon ideas introduced in [jauco/webpack-injectable](https://github.com/jauco/webpack-injectable).
14
+
15
+ ### Usage
16
+
17
+ [Documentation: Using loaders](http://webpack.github.io/docs/using-loaders.html)
18
+
19
+ Use the inject loader by adding the `inject-loader!` [inline loader](https://webpack.js.org/concepts/loaders/#inline) when you use `require`, this will return a function that can used in test code to modify the injected module.
20
+
21
+ By default all `require` statements in an injected module will be altered to be replaced with an injector, though if a replacement it not specified the default values will be used.
22
+
23
+ ### Examples
24
+
25
+ Given some code in a module like this:
26
+
27
+ ```javascript
28
+ // MyStore.js
29
+
30
+ var Dispatcher = require('lib/dispatcher');
31
+ var EventEmitter = require('events').EventEmitter;
32
+ var handleAction = require('lib/handle_action');
33
+
34
+ Dispatcher.register(handleAction, 'MyStore');
35
+ ```
36
+
37
+ You can manipulate it’s dependencies when you come to write tests as follows:
38
+
39
+ ```javascript
40
+ // If no flags are provided when using the loader then
41
+ // all require statements will be wrapped in an injector
42
+ MyModuleInjector = require('inject-loader!MyStore')
43
+ MyModule = MyModuleInjector({
44
+ 'lib/dispatcher': DispatcherMock,
45
+ 'events': EventsMock,
46
+ 'lib/handle_action': HandleActionMock
47
+ })
48
+ ```
49
+
50
+ There are a few examples of complete test setups for both Webpack 1, 2, 3 & 4 in the [`example`](./example) folder.
51
+
52
+ ## License
53
+
54
+ MIT (http://www.opensource.org/licenses/mit-license.php)
@@ -0,0 +1,10 @@
1
+ const injectify = require('./injectify.js');
2
+
3
+ module.exports = function injectifyLoader(source, inputSourceMap) {
4
+ if (this.cacheable) {
5
+ this.cacheable();
6
+ }
7
+
8
+ const {code, map} = injectify(this, source, inputSourceMap);
9
+ this.callback(null, code, map);
10
+ }
@@ -0,0 +1,66 @@
1
+ const {transform, traverse, types: t, transformFromAst} = require('@babel/core');
2
+ const wrapperTemplate = require('./wrapper_template.js');
3
+
4
+ function processRequireCall(path) {
5
+ const dependencyString = path.node.arguments[0].value;
6
+ path.replaceWith(
7
+ t.expressionStatement(
8
+ t.conditionalExpression(
9
+ t.callExpression(
10
+ t.memberExpression(t.identifier('__injections'), t.identifier('hasOwnProperty'), false),
11
+ [t.stringLiteral(dependencyString)]
12
+ ),
13
+ t.memberExpression(t.identifier('__injections'), t.stringLiteral(dependencyString), true),
14
+ path.node
15
+ )
16
+ )
17
+ );
18
+
19
+ return dependencyString;
20
+ }
21
+
22
+ module.exports = function injectify(context, source, inputSourceMap) {
23
+ const {ast} = transform(source, {
24
+ babelrc: false,
25
+ code: false,
26
+ compact: false,
27
+ ast: true,
28
+ filename: context.resourcePath,
29
+ });
30
+
31
+ const dependencies = [];
32
+ traverse(ast, {
33
+ CallExpression(path) {
34
+ if (t.isIdentifier(path.node.callee, {name: 'require'})) {
35
+ dependencies.push(processRequireCall(path));
36
+ path.skip();
37
+ }
38
+ },
39
+ });
40
+
41
+ if (dependencies.length === 0) {
42
+ context.emitWarning(
43
+ "The module you are trying to inject into doesn't have any dependencies. " +
44
+ 'Are you sure you want to do this?'
45
+ );
46
+ }
47
+
48
+ const wrapperModuleAst = t.file(
49
+ t.program([
50
+ wrapperTemplate({
51
+ SOURCE: t.blockStatement(ast.program.body),
52
+ SOURCE_PATH: t.stringLiteral(context.resourcePath),
53
+ DEPENDENCIES: t.arrayExpression(dependencies.map(d => t.stringLiteral(d))),
54
+ }),
55
+ ])
56
+ );
57
+
58
+ return transformFromAst(wrapperModuleAst, source, {
59
+ sourceMaps: context.sourceMap,
60
+ sourceFileName: context.resourcePath,
61
+ inputSourceMap,
62
+ babelrc: false,
63
+ compact: false,
64
+ filename: context.resourcePath,
65
+ });
66
+ }
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "inject-loader",
3
+ "version": "4.0.1",
4
+ "description": "A Webpack loader for injecting code into modules via their dependencies",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "webpack --config config/webpack.config.js",
8
+ "build:test": "webpack --config config/webpack.test.config.js",
9
+ "build:release": "yarn run build && mkdir -p ./dist && cp -f ./tmp/index.js ./dist/index.js && cp -f ./tmp/index.js.map ./dist/index.js.map",
10
+ "pretest:unit": "yarn build && yarn build:test",
11
+ "test:unit": "mocha tmp/testBundle.js --require source-map-support/register",
12
+ "test:integration": "./script/integration_test",
13
+ "test": "flow && yarn test:unit && yarn test:integration",
14
+ "precommit": "pretty-quick --staged"
15
+ },
16
+ "files": [
17
+ "*.md",
18
+ "dist"
19
+ ],
20
+ "author": "Justin Morris <desk@pixelbloom.com> (http://pixelbloom.com)",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git@github.com:plasticine/inject-loader.git"
24
+ },
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "babel-core": "~6"
28
+ },
29
+ "devDependencies": {
30
+ "babel-loader": "^7.1.4",
31
+ "babel-plugin-add-module-exports": "^0.2.1",
32
+ "babel-plugin-transform-flow-strip-types": "^6.22.0",
33
+ "babel-preset-es2015": "^6.22.0",
34
+ "flow-bin": "^0.69.0",
35
+ "husky": "^0.14.3",
36
+ "mocha": "^5.0.5",
37
+ "prettier": "^1.11.1",
38
+ "pretty-quick": "^1.4.1",
39
+ "source-map-support": "^0.5.4",
40
+ "webpack": "^4.35.0",
41
+ "webpack-cli": "^3.3.5"
42
+ },
43
+ "peerDependencies": {
44
+ "webpack": "^1 || ^2 || ^3 || ^4"
45
+ },
46
+ "keywords": [
47
+ "webpack",
48
+ "testing",
49
+ "loader",
50
+ "webpack-loader",
51
+ "inject",
52
+ "mock",
53
+ "mocking"
54
+ ]
55
+ }
@@ -0,0 +1,32 @@
1
+ const {template} = require('@babel/core');
2
+
3
+ module.exports = template(`
4
+ module.exports = function __injector(__injections) {
5
+ __injections = __injections || {};
6
+ (function __validateInjection() {
7
+ var validDependencies = DEPENDENCIES;
8
+ var injectedDependencies = Object.keys(__injections);
9
+ var invalidInjectedDependencies = injectedDependencies.filter(function (dependency) {
10
+ return validDependencies.indexOf(dependency) === -1;
11
+ });
12
+ if (invalidInjectedDependencies.length > 0) {
13
+ var validDependenciesString = ' - ' + validDependencies.join('\\n - ');
14
+ var injectedDependenciesString = ' - ' + injectedDependencies.join('\\n - ');
15
+ var invalidDependenciesString = ' - ' + invalidInjectedDependencies.join('\\n - ');
16
+ throw new Error('Injection Error in ' + SOURCE_PATH + '\\n\\n' +
17
+ 'The following injections are invalid:\\n' + invalidDependenciesString + '\\n\\n' +
18
+ 'The following injections were passed in:\\n' + injectedDependenciesString + '\\n\\n' +
19
+ 'Valid injection targets for this module are:\\n' + validDependenciesString + '\\n'
20
+ );
21
+ }
22
+ })();
23
+ __injector.sourcePath = SOURCE_PATH;
24
+ __injector.validDependencies = DEPENDENCIES;
25
+ var module = { exports: {} };
26
+ var exports = module.exports;
27
+ (function () {
28
+ SOURCE
29
+ })();
30
+ return module.exports;
31
+ }
32
+ `);
@@ -4,19 +4,83 @@ const ModulesConfigLoader = require('./modules-config-loader');
4
4
 
5
5
  class LayoutModulesConfigLoader extends ModulesConfigLoader {
6
6
  /**
7
- * {@inheritdoc}
7
+ * @inheritdoc
8
8
  */
9
9
  loadConfig(theme, filePath) {
10
10
  let themeConfig = super.loadConfig(theme, path.join('Resources/views/layouts/', theme, filePath));
11
+ themeConfig = merge(themeConfig, super.loadConfig(theme, path.join('templates/layouts/', theme, filePath)));
11
12
  // recursive process parent theme
12
13
  const {parent: parentTheme} = this.themes[theme];
13
14
  if (typeof parentTheme === 'string') {
15
+ const processedFiles = this.processedFiles;
14
16
  const parentThemeConfig = this.loadConfig(parentTheme, filePath);
17
+
15
18
  themeConfig = merge(parentThemeConfig, themeConfig);
19
+ // processedFiles from parent theme is added to processedFiles of current theme
20
+ this._processedFiles = [...processedFiles, ...this.processedFiles];
16
21
  }
17
-
18
22
  return themeConfig;
19
23
  }
24
+
25
+ /**
26
+ * All build names:
27
+ * - based on theme names
28
+ * - and theme's extra js builds
29
+ *
30
+ * @return {string[]}
31
+ */
32
+ get buildNames() {
33
+ const buildNames = super.buildNames;
34
+ this.themeNames.forEach(theme => {
35
+ buildNames.push(...this.extraJSBuildNames(theme));
36
+ });
37
+ return buildNames;
38
+ }
39
+
40
+ /**
41
+ * Collect extra js-build names for a theme
42
+ *
43
+ * @param {string} theme name on the theme
44
+ * @return {string[]}
45
+ */
46
+ extraJSBuildNames(theme) {
47
+ const {extra_js_builds: extraJSBuilds = []} = this._themes[theme] || {};
48
+ return [...extraJSBuilds.map(suffix => `${theme}-${suffix}`)];
49
+ }
50
+
51
+ /**
52
+ * Check if buildName is an extra js build of layout theme
53
+ *
54
+ * @param {string} buildName name of the build
55
+ * @return {boolean}
56
+ */
57
+ isExtraJSBuild(buildName) {
58
+ const [, suffix] = this.splitBuildName(buildName);
59
+ return suffix !== void 0;
60
+ }
61
+
62
+ /**
63
+ * Splits build name on parts theme name and suffix (name of extra js build)
64
+ * @param {string} buildName
65
+ * @return {[string]|[string, string]}
66
+ */
67
+ splitBuildName(buildName) {
68
+ // suffix can not contain '-'
69
+ const marches = buildName.match(/(.+)-([^\-]+)?/);
70
+ const result = [];
71
+ if (marches) {
72
+ const [, theme, suffix] = marches;
73
+ const {extra_js_builds: extraJSBuilds = []} = this._themes[theme] || {};
74
+ if (extraJSBuilds.includes(suffix)) {
75
+ result.push(marches[1], marches[2]);
76
+ } else {
77
+ result.push(buildName);
78
+ }
79
+ } else {
80
+ result.push(buildName);
81
+ }
82
+ return result;
83
+ }
20
84
  }
21
85
 
22
86
  module.exports = LayoutModulesConfigLoader;
@@ -2,6 +2,10 @@ const path = require('path');
2
2
  const fs = require('fs');
3
3
  const merge = require('deepmerge');
4
4
  const yaml = require('js-yaml');
5
+ const validation = require('../validation');
6
+
7
+ // merge only unique items
8
+ const arrayMerge = (target, source) => target.concat(source.filter(item => !target.includes(item)));
5
9
 
6
10
  class ModulesConfigLoader {
7
11
  /**
@@ -18,60 +22,83 @@ class ModulesConfigLoader {
18
22
  return Object.keys(this._themes);
19
23
  }
20
24
 
25
+ /**
26
+ * @returns {Array}
27
+ */
28
+ get buildNames() {
29
+ return this.themeNames;
30
+ }
31
+
32
+ /**
33
+ * @returns {Array}
34
+ */
35
+ get processedFiles() {
36
+ return [...this._processedFiles];
37
+ }
38
+
21
39
  /**
22
40
  * @param {Array} bundles Array of ordered symfony bundle paths
23
- * @param {string} themesLocation Path inside the bundle, where to find the theme
24
- * @param {string} themeInfoFileName Yml File name with theme info
41
+ * @param {string|Array} themesLocation Path inside the bundle, where to find the theme
42
+ * @param {string} themeInfoFileName Yaml File name with theme info
25
43
  */
26
44
  constructor(bundles, themesLocation, themeInfoFileName) {
27
45
  this._bundles = bundles;
28
- this._themes = this._getThemes(themesLocation, themeInfoFileName);
46
+ if (!Array.isArray(themesLocation)) {
47
+ themesLocation = [themesLocation];
48
+ }
49
+
50
+ const themes = {};
51
+ const self = this;
52
+ themesLocation.forEach(themesLocation => {
53
+ self._collectThemes(themes, themesLocation, themeInfoFileName);
54
+ });
55
+
56
+ this._themes = themes;
57
+ this._processedFiles = [];
29
58
  }
30
59
 
31
60
  /**
32
- * Return list of themes with their parents
61
+ * Collects list of themes with their parents into given storage(themes param)
62
+ *
63
+ * @param {Object} themes
33
64
  * @param {string} themesLocation
34
65
  * @param {string} themeInfoFileName
35
- * @returns {Object.<string|null>}
36
66
  * @private
37
67
  */
38
- _getThemes(themesLocation, themeInfoFileName) {
39
- const themes = {};
68
+ _collectThemes(themes, themesLocation, themeInfoFileName) {
40
69
  this._bundles.forEach(bundle => {
41
70
  const source = bundle + themesLocation;
42
71
 
43
72
  if (!fs.existsSync(source)) return;
44
73
 
45
74
  fs.readdirSync(source).forEach(name => {
46
- const theme = path.resolve(source, name);
47
- if (!fs.lstatSync(theme).isDirectory()) {
75
+ const themePath = path.resolve(source, name);
76
+ if (!fs.lstatSync(themePath).isDirectory()) {
48
77
  return;
49
78
  }
50
- const themeFile = path.resolve(theme, themeInfoFileName);
79
+ const themeFile = path.resolve(themePath, themeInfoFileName);
51
80
  if (!fs.existsSync(themeFile)) return;
52
81
 
53
- if (!(name in themes)) {
54
- themes[name] = null;
55
- }
56
- themes[name] = yaml.load(fs.readFileSync(themeFile, 'utf8'));
82
+ const theme = yaml.load(fs.readFileSync(themeFile, 'utf8'));
83
+ themes[name] = merge(themes[name] || {}, theme, {arrayMerge});
57
84
  });
58
85
  });
59
-
60
- return themes;
61
86
  }
62
87
 
63
88
  /**
64
89
  * @param {string} theme Theme name
65
90
  * @param {string|string[]} filePath Path (or paths with fallback) to the file inside bundle directory where to find the configs
66
- * @return {Object} Merged Configs loaded from all the bundles Yml files matched by filePath
91
+ * @return {Object} Merged Configs loaded from all the bundles Yaml files matched by filePath
67
92
  */
68
93
  loadConfig(theme, filePath) {
69
94
  let configs = {};
70
95
  const filePaths = [].concat(filePath);
96
+
97
+ this._processedFiles = [];
71
98
  this._bundles.forEach(bundle => {
72
99
  let absolutePath;
73
100
 
74
- for (let file of filePaths) {
101
+ for (const file of filePaths) {
75
102
  absolutePath = path.resolve(bundle, file);
76
103
  if (fs.existsSync(absolutePath)) {
77
104
  break;
@@ -80,10 +107,16 @@ class ModulesConfigLoader {
80
107
  }
81
108
 
82
109
  if (absolutePath) {
83
- const doc = yaml.load(fs.readFileSync(absolutePath, 'utf8'));
110
+ const rawDoc = fs.readFileSync(absolutePath, 'utf8');
111
+ const doc = yaml.load(rawDoc);
112
+
113
+ this._processedFiles.push(absolutePath);
114
+
115
+ validation.checkSchema(absolutePath, doc, theme);
84
116
  configs = merge(configs, doc);
85
117
  }
86
118
  });
119
+
87
120
  return configs;
88
121
  }
89
122
  }