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

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 (43) 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 +89 -2
  11. package/modules-config/modules-config-loader.js +83 -19
  12. package/oro-webpack-config.js +420 -299
  13. package/package.json +39 -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/svg-sprite/index.js +135 -0
  19. package/svg-sprite/svg-sprite-config.js +5 -0
  20. package/svg-sprite/svgo-config.js +32 -0
  21. package/theme-config-factory.js +45 -6
  22. package/utils.js +30 -0
  23. package/validation/assets-validator.js +104 -0
  24. package/validation/errors/assets-input-file-error.js +24 -0
  25. package/validation/errors/assets-schema-error.js +40 -0
  26. package/validation/errors/base-error.js +37 -0
  27. package/validation/errors/jsmodules-extra-modules-error.js +22 -0
  28. package/validation/errors/jsmodules-schema-error.js +40 -0
  29. package/validation/errors/styles-error.js +24 -0
  30. package/validation/errors/svg-icons-schema-error.js +36 -0
  31. package/validation/index.js +41 -0
  32. package/validation/jsmodules-validator.js +55 -0
  33. package/validation/schema-validator.js +62 -0
  34. package/validation/schemas/assets-schema-full.js +22 -0
  35. package/validation/schemas/assets-schema.js +32 -0
  36. package/validation/schemas/jsmodules-schema-full.js +11 -0
  37. package/validation/schemas/jsmodules-schema.js +76 -0
  38. package/validation/schemas/svg-icons-schema-full.js +18 -0
  39. package/validation/schemas/svg-icons-schema.js +17 -0
  40. package/validation/svg-icons-validator.js +53 -0
  41. package/writer/configs-file-writer.js +1 -1
  42. package/writer/dynamic-imports-file-writer.js +3 -3
  43. package/writer/scss-entry-point-file-writer.js +1 -1
package/package.json CHANGED
@@ -1,49 +1,50 @@
1
1
  {
2
2
  "name": "@oroinc/oro-webpack-config-builder",
3
- "version": "5.1.0-alpha9",
3
+ "version": "5.1.0-dev0010",
4
4
  "author": "Oro, Inc (https://www.oroinc.com)",
5
5
  "license": "MIT",
6
6
  "description": "An integration of OroPlatform based applications with the Webpack.",
7
7
  "main": "oro-webpack-config.js",
8
8
  "dependencies": {
9
- "@babel/core": "^7.10.4",
10
- "@babel/plugin-transform-runtime": "^7.10.4",
11
- "@babel/preset-env": "^7.10.4",
12
- "autoprefixer": "^9.8.6",
13
- "babel-loader": "^8.1.0",
14
- "bindings": "^1.5.0",
15
- "css-loader": "^5.0.2",
16
- "css-minimizer-webpack-plugin": "^1.2.0",
17
- "deepmerge": "^4.2.2",
18
- "exports-loader": "^2.0.0",
19
- "expose-loader": "^2.0.0",
20
- "extract-loader": "^5.1.0",
21
- "file-loader": "^6.2.0",
22
- "happypack": "^5.0.1",
23
- "html-webpack-plugin": "^5.2.0",
24
- "imports-loader": "^2.0.0",
25
- "js-yaml": "^4.0.0",
26
- "mini-css-extract-plugin": "^1.3.8",
27
- "minimist": "^1.2.3",
28
- "nan": "^2.14.2",
9
+ "@babel/core": "~7.21.3",
10
+ "@babel/plugin-transform-runtime": "~7.21.0",
11
+ "@babel/preset-env": "~7.21.2",
12
+ "autoprefixer": "~10.4.13",
13
+ "babel-loader": "~9.1.0",
14
+ "bindings": "~1.5.0",
15
+ "css-loader": "^6.7.3",
16
+ "css-minimizer-webpack-plugin": "~5.0.0",
17
+ "deepmerge": "~4.3.1",
18
+ "exports-loader": "~4.0.0",
19
+ "expose-loader": "~4.1.0",
20
+ "file-loader": "~6.2.0",
21
+ "html-webpack-plugin": "~5.5.0",
22
+ "imports-loader": "~4.0.1",
23
+ "js-yaml": "~4.1.0",
24
+ "mini-css-extract-plugin": "~2.7.1",
25
+ "minimist": "~1.2.7",
26
+ "nan": "~2.17.0",
29
27
  "path": "0.12.7",
30
- "postcss": "^8.2.9",
31
- "postcss-loader": "^5.0.0",
32
- "printf": "^0.6.0",
33
- "resolve-url-loader": "^3.1.2",
34
- "rtlcss-webpack-plugin": "^4.0.6",
35
- "sass": "^1.32.8",
36
- "sass-loader": "^11.0.1",
37
- "style-loader": "^2.0.0",
38
- "terser": "^5.6.0",
28
+ "postcss": "~8.4.23",
29
+ "postcss-loader": "~7.2.4",
30
+ "printf": "~0.6.0",
31
+ "resolve-url-loader": "^5.0.0",
32
+ "rtlcss-webpack-plugin": "~4.0.6",
33
+ "sass": "~1.62.0",
34
+ "sass-loader": "~13.2.0",
35
+ "schema-utils": "^4.0.0",
36
+ "style-loader": "~3.3.1",
37
+ "svgo": "^3.0.2",
38
+ "svgstore": "^3.0.1",
39
+ "terser": "~5.17.1",
39
40
  "text-loader": "0.0.1",
40
- "underscore": "^1.10.2",
41
- "url-loader": "^4.1.1",
42
- "webpack": "^5.23.0",
43
- "webpack-bundle-analyzer": "^4.4.0",
44
- "webpack-cli": "^4.5.0",
45
- "webpack-dev-server": "^3.11.0",
46
- "webpack-merge": "^5.7.3",
47
- "wildcard": "^2.0.0"
41
+ "underscore": "1.13.*",
42
+ "url-loader": "~4.1.1",
43
+ "webpack": "~5.80.0",
44
+ "webpack-bundle-analyzer": "~4.8.0",
45
+ "webpack-cli": "~5.0.0",
46
+ "webpack-dev-server": "^4.11.1",
47
+ "webpack-merge": "~5.8.0",
48
+ "wildcard": "~2.0.0"
48
49
  }
49
50
  }
@@ -0,0 +1,25 @@
1
+ const {once} = require('underscore');
2
+
3
+ class AfterWebpackLogsPlugin {
4
+ /**
5
+ * @param {function} afterLogsCb
6
+ */
7
+ constructor(afterLogsCb) {
8
+ if (typeof afterLogsCb !== 'function') {
9
+ throw new Error('The "afterLogsCb" arg should be a function');
10
+ }
11
+ this._afterLogsCb = once(afterLogsCb);
12
+ }
13
+
14
+ apply(compiler) {
15
+ compiler.hooks.done.tap('AfterWebpackLogsPlugin', stats => {
16
+ const compilerStats = stats;
17
+ stats.compilation.hooks.statsPrinter.tap('AfterWebpackLogsPlugin', stats => {
18
+ // Making logs to be after all webpack logs in the console
19
+ setImmediate(() => this._afterLogsCb(compilerStats));
20
+ });
21
+ });
22
+ }
23
+ }
24
+
25
+ module.exports = AfterWebpackLogsPlugin;
@@ -3,19 +3,20 @@ const StyleLoader = require('./style-loader');
3
3
 
4
4
  class AdminStyleLoader extends StyleLoader {
5
5
  /**
6
- * @inheritDoc
6
+ * @inheritdoc
7
7
  */
8
8
  _fetchThemeConfig(themeName) {
9
9
  const {rtl_support: rtlSupport = false, styles: extraThemeConfig} =
10
10
  this._configLoader.loadConfig(themeName, 'Resources/public/themes/' + themeName + '/settings.yml');
11
11
  const baseThemeConfig = this._configLoader.loadConfig(themeName, 'Resources/config/oro/assets.yml');
12
+ const appRootExtraConfig = this._configLoader.loadConfig(themeName, 'config/oro/assets.yml');
12
13
  /** @type {Object.<string, ThemeGroupConfig>} */
13
- const themeConfig = merge({}, baseThemeConfig, extraThemeConfig);
14
+ const themeConfig = merge(baseThemeConfig, appRootExtraConfig, extraThemeConfig);
14
15
 
15
16
  return {
16
17
  themeConfig,
17
18
  settings: {rtlSupport}
18
- }
19
+ };
19
20
  }
20
21
  }
21
22
 
@@ -1,8 +1,8 @@
1
1
  const StyleLoader = require('./style-loader');
2
2
 
3
- class LayoutStyleLoader extends StyleLoader {
3
+ class LayoutStyleLoader extends StyleLoader {
4
4
  /**
5
- * @inheritDoc
5
+ * @inheritdoc
6
6
  */
7
7
  _fetchThemeConfig(themeName) {
8
8
  const {rtl_support: rtlSupport = false} = this._configLoader.themes[themeName];
@@ -12,7 +12,7 @@ class LayoutStyleLoader extends StyleLoader {
12
12
  return {
13
13
  themeConfig,
14
14
  settings: {rtlSupport}
15
- }
15
+ };
16
16
  }
17
17
  }
18
18
 
@@ -1,6 +1,9 @@
1
1
  const path = require('path');
2
2
  const wildcard = require('wildcard');
3
- const _ = require('underscore')
3
+ const _ = require('underscore');
4
+ const {isProdMode} = require('../utils');
5
+ const {assetsValidation} = require('../validation');
6
+ const StylesError = require('../validation/errors/styles-error');
4
7
 
5
8
  /**
6
9
  * @typedef ThemeGroupConfig
@@ -29,16 +32,24 @@ class StyleLoader {
29
32
  getThemeEntryPoints(theme, buildPath) {
30
33
  const {themeConfig, settings = {}} = this._fetchThemeConfig(theme);
31
34
 
35
+ assetsValidation.checkFullSchema(themeConfig, this._configLoader.processedFiles, theme);
36
+
32
37
  const entryPoints = {};
33
38
  const writingOptions = {};
34
39
  for (const [group, config] of Object.entries(themeConfig)) {
35
40
  let {inputs, entries = [], output, auto_rtl_inputs: rtlMasks = []} = config;
36
- if (output === undefined) {
37
- throw new Error('"output" for "' + group + '" group in theme "' + theme + '" is not defined');
41
+
42
+ if (isProdMode() && output === void 0) {
43
+ throw new StylesError('output', group, theme);
44
+ }
45
+
46
+ if (isProdMode() && inputs === void 0) {
47
+ throw new StylesError('inputs', group, theme);
38
48
  }
39
49
 
40
50
  inputs = this._overrideInputs(inputs);
41
51
  inputs = this._sortInputs(inputs);
52
+ inputs = this._applyInputsBasePathPrefix(inputs);
42
53
 
43
54
  if (settings.rtlSupport) {
44
55
  writingOptions.ignoreRTLInputs = _.difference(inputs, this._matchInputs(rtlMasks, inputs));
@@ -48,7 +59,7 @@ class StyleLoader {
48
59
  const filePath = path.join(buildPath, output);
49
60
  entryPoints[entryPointName] = [
50
61
  ...entries,
51
- this._entryPointFileWriter.write('./../../../', inputs, filePath, writingOptions)
62
+ this._entryPointFileWriter.write('./../../', inputs, filePath, writingOptions)
52
63
  ];
53
64
  }
54
65
  return entryPoints;
@@ -83,7 +94,7 @@ class StyleLoader {
83
94
  const oldInputIndex = mappedInputs.findIndex(item => item === oldInput);
84
95
 
85
96
  if (oldInputIndex === -1) {
86
- // old input does not exists any more
97
+ // old input does not exist anymore
87
98
  mappedInputs.push(newInput);
88
99
  } else if (newInput) {
89
100
  // replace input
@@ -105,20 +116,33 @@ class StyleLoader {
105
116
  * @protected
106
117
  */
107
118
  _sortInputs(inputs) {
119
+ const primarySettingsInputs = [];
108
120
  const settingsInputs = [];
121
+ const primaryVariablesInputs = [];
109
122
  const variablesInputs = [];
110
123
  const restInputs = [];
111
124
 
112
125
  inputs.forEach(input => {
113
- if (input.indexOf('/settings/') > 0) {
126
+ if (input.indexOf('/settings/primary-settings') > 0) {
127
+ primarySettingsInputs.push(input);
128
+ } else if (input.indexOf('/settings/') > 0) {
114
129
  settingsInputs.push(input);
130
+ } else if (input.indexOf('/variables/primary-variables') > 0) {
131
+ primaryVariablesInputs.push(input);
115
132
  } else if (input.indexOf('/variables/') > 0) {
116
133
  variablesInputs.push(input);
117
134
  } else {
118
135
  restInputs.push(input);
119
136
  }
120
137
  });
121
- return [...settingsInputs, ...variablesInputs, ...restInputs];
138
+
139
+ return [
140
+ ...primarySettingsInputs,
141
+ ...settingsInputs,
142
+ ...primaryVariablesInputs,
143
+ ...variablesInputs,
144
+ ...restInputs
145
+ ];
122
146
  }
123
147
 
124
148
  /**
@@ -130,6 +154,7 @@ class StyleLoader {
130
154
  * @protected
131
155
  */
132
156
  _matchInputs(masks, inputs) {
157
+ masks = this._applyInputsBasePathPrefix(masks);
133
158
  masks = masks.map(mask => wildcard(mask));
134
159
 
135
160
  const whiteListedInputs = masks.reduce((include, mask) => {
@@ -139,6 +164,26 @@ class StyleLoader {
139
164
 
140
165
  return _.unique(whiteListedInputs);
141
166
  }
167
+
168
+ /**
169
+ * Considering base path as application's root.
170
+ * Bundles based *.scss sources go with '../' prefix.
171
+ *
172
+ * @param {string[]} inputs
173
+ * @private
174
+ */
175
+ _applyInputsBasePathPrefix(inputs) {
176
+ const processedInputs = [];
177
+
178
+ inputs.forEach(input => {
179
+ if (input.indexOf('bundles') === 0) {
180
+ input = '../' + input;
181
+ }
182
+ processedInputs.push(input);
183
+ });
184
+
185
+ return processedInputs;
186
+ }
142
187
  }
143
188
 
144
189
  module.exports = StyleLoader;
@@ -0,0 +1,135 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const {svgIconsValidator} = require('../validation');
4
+ const svgoConfig = require('./svgo-config');
5
+ const svgSpriteConfig = require('./svg-sprite-config');
6
+ const {optimize} = require('svgo');
7
+ const svgstore = require('svgstore');
8
+ class SvgSprite {
9
+ /**
10
+ * @param {ModulesConfigLoader} configLoader
11
+ * @param {string} theme
12
+ * @param {string} publicPath
13
+ * @param {string} buildPublicPath
14
+ */
15
+ constructor(configLoader, theme, publicPath, buildPublicPath) {
16
+ this.SPRITE_NAME = 'theme-icons';
17
+ this._configLoader = configLoader;
18
+ this._theme = theme;
19
+ this._publicPath = publicPath;
20
+ this._buildPublicPath = buildPublicPath;
21
+
22
+ this.optimize();
23
+ }
24
+
25
+ /**
26
+ * Starting optimization
27
+ */
28
+ optimize() {
29
+ const svgConfig = this._configLoader.loadConfig(
30
+ this._theme,
31
+ 'config/svg-icons.yml'
32
+ );
33
+
34
+ svgIconsValidator.checkFullSchema(svgConfig, this._configLoader.processedFiles, this._theme);
35
+
36
+ const svgPaths = this._configLoader.getFilesPaths(
37
+ this._theme,
38
+ 'Resources/public/',
39
+ '.svg'
40
+ );
41
+ const exclude = svgConfig.exclude ?? [];
42
+ const filesToOptimize = [];
43
+ const filesNames = {};
44
+
45
+ svgPaths.forEach(icon => {
46
+ const svgName = path.parse(icon).name;
47
+
48
+ if (svgName === this.SPRITE_NAME) {
49
+ throw new Error(
50
+ `The "${this.SPRITE_NAME}" is a reserved word and cannot be used as a svg name for building sprite.`
51
+ );
52
+ }
53
+
54
+ const toExclude = exclude.includes(svgName);
55
+ const fileIsChosenToMove = filesNames[svgName] === svgName;
56
+
57
+ if (toExclude === false && fileIsChosenToMove === false) {
58
+ filesToOptimize.push(icon);
59
+ filesNames[svgName] = svgName;
60
+ }
61
+ });
62
+
63
+ if (filesToOptimize.length === 0) {
64
+ return;
65
+ }
66
+
67
+ this.moveFiles(filesToOptimize);
68
+ }
69
+
70
+ /**
71
+ * Preparing files for optimization
72
+ * @param {Array} files
73
+ * @returns {Promise<void>}
74
+ */
75
+ async moveFiles(files) {
76
+ const buildPath = path.join(this._buildPublicPath, 'svg-icons');
77
+ const dirPath = path.resolve(`${this._publicPath}${buildPath}`);
78
+
79
+ if (!fs.existsSync(dirPath)) {
80
+ fs.mkdirSync(dirPath, {recursive: true});
81
+ }
82
+
83
+ try {
84
+ fs.readdirSync(dirPath).forEach(file => {
85
+ fs.unlinkSync(path.join(dirPath, file));
86
+ });
87
+ } catch (error) {
88
+ throw new Error(error);
89
+ }
90
+
91
+ await Promise.all(
92
+ files.map(file => this.optimizeFile(file, dirPath))
93
+ );
94
+ this.createSprite(fs.readdirSync(dirPath), dirPath);
95
+ }
96
+
97
+ /**
98
+ * Optimizing svg using svgo
99
+ * @param {Object} file
100
+ * @param {string} dirPath
101
+ * @returns {Promise<void>}
102
+ */
103
+ async optimizeFile(file, dirPath) {
104
+ const {name, ext} = path.parse(file);
105
+ const filepath = path.join(dirPath, `${name}${ext}`);
106
+
107
+ const originalSvg = fs.readFileSync(file, 'utf8');
108
+ const {data: optimizedSvg} = await optimize(originalSvg, {
109
+ path: filepath,
110
+ ...svgoConfig
111
+ });
112
+
113
+ // svgo will always add a final newline when in pretty mode
114
+ const resultSvg = optimizedSvg.trim().replace(/(\n\r?|\r\n?)/g, '');
115
+ fs.writeFileSync(filepath, resultSvg, 'utf8');
116
+ }
117
+
118
+ /**
119
+ * Creating sprite
120
+ * @param {Array} files
121
+ * @param {string} dirPath
122
+ */
123
+ createSprite(files, dirPath) {
124
+ const sprites = svgstore(svgSpriteConfig);
125
+
126
+ for (const file of files) {
127
+ const svgName = path.parse(file).name;
128
+ sprites.add(svgName, fs.readFileSync(path.join(dirPath, file), 'utf8'));
129
+ }
130
+
131
+ fs.writeFileSync(path.join(dirPath, `${this.SPRITE_NAME}.svg`), sprites, 'utf8');
132
+ }
133
+ }
134
+
135
+ module.exports = SvgSprite;
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ cleanDefs: false,
3
+ cleanSymbols: false,
4
+ inline: false
5
+ };
@@ -0,0 +1,32 @@
1
+ module.exports = {
2
+ multipass: true,
3
+ js2svg: {
4
+ pretty: true,
5
+ indent: 0,
6
+ eol: 'lf'
7
+ },
8
+ plugins: [{
9
+ name: 'preset-default',
10
+ params: {
11
+ overrides: {
12
+ removeUnknownsAndDefaults: {
13
+ keepRoleAttr: true
14
+ },
15
+ removeViewBox: false
16
+ }
17
+ }
18
+ },
19
+ // The next plugins are included in svgo but are not part of preset-default,
20
+ // so we need to enable them separately
21
+ 'cleanupListOfValues',
22
+ {
23
+ name: 'removeAttrs',
24
+ params: {
25
+ attrs: [
26
+ 'clip-rule',
27
+ 'data-name',
28
+ 'fill'
29
+ ]
30
+ }
31
+ }]
32
+ };
@@ -1,3 +1,5 @@
1
+ const JSModulesExtraModulesError = require('./validation/errors/jsmodules-extra-modules-error');
2
+
1
3
  class ThemeConfigFactory {
2
4
  /**
3
5
  * @param {ModulesConfigLoader} configLoader
@@ -11,22 +13,59 @@ class ThemeConfigFactory {
11
13
  this._appModulesFileWriter = appModulesFileWriter;
12
14
  this._configsFileWriter = configsFileWriter;
13
15
  }
14
-
15
16
  /**
16
17
  * @param {string} theme Theme name
17
- * @param {string} buildPath Path to theme build folder
18
18
  * @param {string|string[]} configFilepath Path (or paths with fallback) to yaml config file in a bundle
19
- * @return {Object} List of Webpack entry-points
19
+ * @return {Object} Merged Configs loaded from all the bundles Yaml files matched by filePath
20
+ */
21
+ loadConfig(theme, configFilepath) {
22
+ return this._configLoader.loadConfig(theme, configFilepath);
23
+ }
24
+
25
+ extendConfig(baseConfig, extraConfig) {
26
+ const {
27
+ aliases = {},
28
+ configs,
29
+ map = {},
30
+ shim = {}
31
+ } = baseConfig;
32
+
33
+ const {
34
+ ['app-modules']: appModules,
35
+ ['dynamic-imports']: dynamicImports,
36
+ entry,
37
+ ...rest
38
+ } = extraConfig;
39
+
40
+ const beyondKeys = Object.keys(rest);
41
+ if (beyondKeys.length) {
42
+ throw new JSModulesExtraModulesError(beyondKeys);
43
+ }
44
+
45
+ return {
46
+ aliases,
47
+ 'app-modules': appModules,
48
+ configs,
49
+ 'dynamic-imports': dynamicImports,
50
+ entry,
51
+ map,
52
+ shim
53
+ };
54
+ }
55
+
56
+ /**
57
+ * @param {string} buildPath Path to theme build folder
58
+ * @param {Object} jsModulesConfig configuration loaded from jsmodules files and merged together
59
+ * @return {Object} webpack configuration fragment
20
60
  */
21
- create(theme, buildPath, configFilepath) {
22
- const jsModulesConfig = this._configLoader.loadConfig(theme, configFilepath);
61
+ create(buildPath, jsModulesConfig) {
23
62
  const {
24
63
  entry,
25
64
  map = {},
26
65
  shim = {},
27
66
  configs,
28
67
  aliases = {},
29
- ['app-modules']: appModules,
68
+ ['app-modules']: appModules = [],
30
69
  ['dynamic-imports']: dynamicImports
31
70
  } = jsModulesConfig;
32
71
 
package/utils.js ADDED
@@ -0,0 +1,30 @@
1
+ let prodMode;
2
+ let verboseMode;
3
+
4
+ module.exports = {
5
+ isVerboseMode() {
6
+ if (verboseMode === void 0) {
7
+ verboseMode = process.argv.filter(arg => {
8
+ // Depending on how the command was run (-v, -vv, -vvv, --verbose) -
9
+ // arguments can be present in different formats: stats=normal, stats=detailed, stats=verbose, verbose
10
+ return arg.search(/^verbose|(^stats=(normal|detailed|verbose))/) !== -1;
11
+ }).length > 0;
12
+ }
13
+
14
+ return verboseMode;
15
+ },
16
+ isProdMode() {
17
+ if (prodMode !== void 0) {
18
+ return prodMode;
19
+ }
20
+
21
+ process.argv.forEach(arg => {
22
+ if (prodMode) {
23
+ return;
24
+ }
25
+ prodMode = ['--mode=production', '--env=prod'].some(item => item === arg);
26
+ });
27
+
28
+ return prodMode;
29
+ }
30
+ };
@@ -0,0 +1,104 @@
1
+ const {isProdMode} = require('../utils');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const schema = require('./schemas/assets-schema');
5
+ const fullSchema = require('./schemas/assets-schema-full');
6
+ const schemaValidator = require('./schema-validator');
7
+ const EventEmitter = require('events');
8
+ const emitter = new EventEmitter();
9
+ const AssetsSchemaError = require('./errors/assets-schema-error');
10
+ const AssetsInputFileError = require('./errors/assets-input-file-error');
11
+
12
+ module.exports = Object.assign({}, schemaValidator, {
13
+ emitter,
14
+
15
+ /**
16
+ * @param {string} filePath
17
+ * @param {Object} doc
18
+ * @param {string} theme
19
+ * @returns {boolean|undefined}
20
+ */
21
+ checkSchema(filePath, doc, theme) {
22
+ if (isProdMode()) {
23
+ return;
24
+ }
25
+ const result = this.validateSchema(schema, doc);
26
+
27
+ if (!result.valid) {
28
+ const error = new AssetsSchemaError(result.formattedError, [filePath], theme);
29
+
30
+ this.emitter.emit('error', error);
31
+ }
32
+
33
+ return result.valid;
34
+ },
35
+
36
+ /**
37
+ * @param {Object} doc
38
+ * @param {Array} files
39
+ * @param {string} theme
40
+ * @returns {boolean|undefined}
41
+ */
42
+ checkFullSchema(doc, files = [], theme) {
43
+ if (isProdMode()) {
44
+ return;
45
+ }
46
+ const result = this.validateSchema(fullSchema, doc);
47
+
48
+ if (!result.valid) {
49
+ const error = new AssetsSchemaError(result.formattedError, files, theme);
50
+
51
+ this.emitter.emit('error', error);
52
+ }
53
+
54
+ return result.valid;
55
+ },
56
+
57
+ /**
58
+ * @param {string} filePath
59
+ * @param {Object} doc
60
+ * @param {string} theme
61
+ * @returns {boolean|undefined}
62
+ */
63
+ checkInputsExist(filePath, doc, theme) {
64
+ if (isProdMode()) {
65
+ return;
66
+ }
67
+
68
+ let valid = true;
69
+ for (const [, props] of Object.entries(doc)) {
70
+ if (!Array.isArray(props.inputs)) {
71
+ continue;
72
+ }
73
+
74
+ const files = [];
75
+ props.inputs.forEach(input => {
76
+ if (typeof input === 'object') {
77
+ const newPath = Object.values(input)[0];
78
+
79
+ if (newPath === null) {
80
+ return;
81
+ }
82
+ input = newPath;
83
+ }
84
+
85
+ const fullBasePath = path.resolve(input);
86
+ const fullPath = path.resolve(this._publicPath + input);
87
+ // skip the path to global node modules,
88
+ // e.g. '~bootstrap/scss/bootstrap'
89
+ if (!input.startsWith('~') && !fs.existsSync(fullPath) && !fs.existsSync(fullBasePath)) {
90
+ files.push(input);
91
+ }
92
+ });
93
+ if (files.length) {
94
+ valid = false;
95
+ files.forEach(notExistFile => {
96
+ const error = new AssetsInputFileError(notExistFile, filePath);
97
+
98
+ this.emitter.emit('error', error);
99
+ });
100
+ }
101
+ }
102
+ return valid;
103
+ }
104
+ });