ee-core 1.0.0

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -0
  3. package/bin/tools.js +22 -0
  4. package/config/config.default.js +179 -0
  5. package/core/index.js +13 -0
  6. package/core/lib/ee.js +220 -0
  7. package/core/lib/loader/context_loader.js +105 -0
  8. package/core/lib/loader/ee_loader.js +427 -0
  9. package/core/lib/loader/file_loader.js +262 -0
  10. package/core/lib/loader/mixin/config.js +138 -0
  11. package/core/lib/loader/mixin/controller.js +123 -0
  12. package/core/lib/loader/mixin/service.js +29 -0
  13. package/core/lib/utils/base_context_class.js +34 -0
  14. package/core/lib/utils/index.js +100 -0
  15. package/core/lib/utils/sequencify.js +59 -0
  16. package/core/lib/utils/timing.js +77 -0
  17. package/index.js +50 -0
  18. package/lib/appLoader.js +45 -0
  19. package/lib/application.js +69 -0
  20. package/lib/baseApp.js +155 -0
  21. package/lib/constant.js +30 -0
  22. package/lib/eeApp.js +306 -0
  23. package/lib/helper.js +52 -0
  24. package/lib/httpclient.js +136 -0
  25. package/lib/logger.js +47 -0
  26. package/lib/socket/io.js +22 -0
  27. package/lib/socket/ipcServer.js +112 -0
  28. package/lib/socket/socketClient.js +51 -0
  29. package/lib/socket/socketServer.js +70 -0
  30. package/lib/socket/start.js +18 -0
  31. package/lib/storage/appStorage.js +14 -0
  32. package/lib/storage/index.js +22 -0
  33. package/lib/storage/lowdbStorage.js +144 -0
  34. package/package.json +39 -0
  35. package/resource/images/loding.gif +0 -0
  36. package/resource/images/tray_logo.png +0 -0
  37. package/resource/loading.html +22 -0
  38. package/resource/view_example.html +22 -0
  39. package/tools/codeCompress.js +202 -0
  40. package/tools/replaceDist.js +71 -0
  41. package/utils/index.js +141 -0
  42. package/utils/wrap.js +38 -0
@@ -0,0 +1,427 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const assert = require('assert');
6
+ const is = require('is-type-of');
7
+ const debug = require('debug')('ee-core:EeLoader');
8
+ const FileLoader = require('./file_loader');
9
+ const ContextLoader = require('./context_loader');
10
+ const utility = require('utility');
11
+ const utils = require('../utils');
12
+ const Timing = require('../utils/timing');
13
+
14
+ const REQUIRE_COUNT = Symbol('EeLoader#requireCount');
15
+
16
+ class EeLoader {
17
+
18
+ /**
19
+ * @class
20
+ * @param {Object} options - options
21
+ * @param {String} options.baseDir - the directory of application
22
+ * @param {EeCore} options.app - Application instance
23
+ * @param {Logger} options.logger - logger
24
+ * @since 1.0.0
25
+ */
26
+ constructor(options) {
27
+ this.options = options;
28
+ assert(fs.existsSync(this.options.baseDir), `${this.options.baseDir} not exists`);
29
+ assert(this.options.app, 'options.app is required');
30
+ assert(this.options.logger, 'options.logger is required');
31
+
32
+ this.app = this.options.app;
33
+ this.timing = this.app.timing || new Timing();
34
+ this[REQUIRE_COUNT] = 0;
35
+
36
+ /**
37
+ * @member {Object} EeLoader#pkg
38
+ * @see {@link AppInfo#pkg}
39
+ * @since 1.0.0
40
+ */
41
+ this.pkg = utility.readJSONSync(path.join(this.options.homeDir, 'package.json'));
42
+
43
+ /**
44
+ * Framework directories
45
+ *
46
+ * @member {Array} EeLoader#EePaths
47
+ * @see EeLoader#getEePaths
48
+ * @since 1.0.0
49
+ */
50
+ this.EePaths = this.getEePaths();
51
+ debug('Loaded EePaths %j', this.EePaths);
52
+
53
+ /**
54
+ * @member {String} EeLoader#serverEnv
55
+ * @see AppInfo#env
56
+ * @since 1.0.0
57
+ */
58
+ this.serverEnv = this.getServerEnv();
59
+ debug('Loaded serverEnv %j', this.serverEnv);
60
+
61
+ /**
62
+ * @member {AppInfo} EeLoader#appInfo
63
+ * @since 1.0.0
64
+ */
65
+ this.appInfo = this.getAppInfo();
66
+
67
+ /**
68
+ * @member {String} EeLoader#serverScope
69
+ * @see AppInfo#serverScope
70
+ */
71
+ this.serverScope = options.serverScope !== undefined
72
+ ? options.serverScope
73
+ : this.getServerScope();
74
+ }
75
+
76
+ /**
77
+ * Get {@link AppInfo#env}
78
+ * @return {String} env
79
+ * @see AppInfo#env
80
+ * @private
81
+ * @since 1.0.0
82
+ */
83
+ getServerEnv() {
84
+ let serverEnv = this.options.env;
85
+
86
+ const envPath = path.join(this.options.baseDir, 'config/env');
87
+ if (!serverEnv && fs.existsSync(envPath)) {
88
+ serverEnv = fs.readFileSync(envPath, 'utf8').trim();
89
+ }
90
+
91
+ if (!serverEnv) {
92
+ serverEnv = process.env.EE_SERVER_ENV;
93
+ }
94
+
95
+ if (!serverEnv) {
96
+ if (process.env.NODE_ENV === 'test') {
97
+ serverEnv = 'unittest';
98
+ } else if (process.env.NODE_ENV === 'production') {
99
+ serverEnv = 'prod';
100
+ } else {
101
+ serverEnv = 'local';
102
+ }
103
+ } else {
104
+ serverEnv = serverEnv.trim();
105
+ }
106
+
107
+ return serverEnv;
108
+ }
109
+
110
+ /**
111
+ * Get {@link AppInfo#scope}
112
+ * @return {String} serverScope
113
+ * @private
114
+ */
115
+ getServerScope() {
116
+ return process.env.EE_SERVER_SCOPE || '';
117
+ }
118
+
119
+ /**
120
+ * Get {@link AppInfo#name}
121
+ * @return {String} appname
122
+ * @private
123
+ * @since 1.0.0
124
+ */
125
+ getAppname() {
126
+ if (this.pkg.name) {
127
+ debug('Loaded appname(%s) from package.json', this.pkg.name);
128
+ return this.pkg.name;
129
+ }
130
+ const pkg = path.join(this.options.baseDir, 'package.json');
131
+ throw new Error(`name is required from ${pkg}`);
132
+ }
133
+
134
+ /**
135
+ * Get home directory
136
+ * @return {String} home directory
137
+ * @since 3.4.0
138
+ */
139
+ getHomedir() {
140
+ return this.options.homeDir;
141
+ }
142
+
143
+ /**
144
+ * Get app info
145
+ * @return {AppInfo} appInfo
146
+ * @since 1.0.0
147
+ */
148
+ getAppInfo() {
149
+ const env = this.serverEnv;
150
+ const scope = this.serverScope;
151
+ const home = this.getHomedir();
152
+ const baseDir = this.options.baseDir;
153
+ const appUserDataDir = this.options.appUserData;
154
+
155
+ /**
156
+ * Meta information of the application
157
+ * @class AppInfo
158
+ */
159
+ return {
160
+ /**
161
+ * The name of the application, retrieve from the name property in `package.json`.
162
+ * @member {String} AppInfo#name
163
+ */
164
+ name: this.getAppname(),
165
+
166
+ /**
167
+ * The current directory, where the application code is.
168
+ * @member {String} AppInfo#baseDir
169
+ */
170
+ baseDir,
171
+
172
+ /**
173
+ * The environment of the application, **it's not NODE_ENV**
174
+ *
175
+ * 1. from `$baseDir/config/env`
176
+ * 2. from EE_SERVER_ENV
177
+ * 3. from NODE_ENV
178
+ *
179
+ * env | description
180
+ * --- | ---
181
+ * test | system integration testing
182
+ * prod | production
183
+ * local | local on your own computer
184
+ * unittest | unit test
185
+ *
186
+ * @member {String} AppInfo#env
187
+ * @see https://Eejs.org/zh-cn/basics/env.html
188
+ */
189
+ env,
190
+
191
+ /**
192
+ * @member {String} AppInfo#scope
193
+ */
194
+ scope,
195
+
196
+ /**
197
+ * The use directory, same as `process.env.HOME`
198
+ * @member {String} AppInfo#HOME
199
+ */
200
+ home: home,
201
+
202
+ /**
203
+ * parsed from `package.json`
204
+ * @member {Object} AppInfo#pkg
205
+ */
206
+ pkg: this.pkg,
207
+
208
+ /**
209
+ * The directory whether is baseDir or HOME depend on env.
210
+ * it's good for test when you want to write some file to HOME,
211
+ * but don't want to write to the real directory,
212
+ * so use root to write file to baseDir instead of HOME when unittest.
213
+ * keep root directory in baseDir when local and unittest
214
+ * @member {String} AppInfo#root
215
+ */
216
+ root: env === 'local' || env === 'unittest' ? home : appUserDataDir,
217
+
218
+ appUserDataDir: appUserDataDir
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Get {@link EeLoader#EePaths}
224
+ * @return {Array} framework directories
225
+ * @see {@link EeLoader#EePaths}
226
+ * @private
227
+ * @since 1.0.0
228
+ */
229
+ getEePaths() {
230
+ // avoid require recursively
231
+ const EePaths = [];
232
+ EePaths.push(this.app[Symbol.for('ee#eePath')]);
233
+
234
+ return EePaths;
235
+ }
236
+
237
+ // Low Level API
238
+
239
+ /**
240
+ * Load single file, will invoke when export is function
241
+ *
242
+ * @param {String} filepath - fullpath
243
+ * @param {Array} inject - pass rest arguments into the function when invoke
244
+ * @return {Object} exports
245
+ * @example
246
+ * ```js
247
+ * app.loader.loadFile(path.join(app.options.baseDir, 'config/router.js'));
248
+ * ```
249
+ * @since 1.0.0
250
+ */
251
+ loadFile(filepath, ...inject) {
252
+ filepath = filepath && this.resolveModule(filepath);
253
+ if (!filepath) {
254
+ return null;
255
+ }
256
+
257
+ // function(arg1, args, ...) {}
258
+ if (inject.length === 0) inject = [ this.app ];
259
+
260
+ let ret = this.requireFile(filepath);
261
+ if (is.function(ret) && !is.class(ret)) {
262
+ ret = ret(...inject);
263
+ }
264
+ return ret;
265
+ }
266
+
267
+ /**
268
+ * @param {String} filepath - fullpath
269
+ * @return {Object} exports
270
+ * @private
271
+ */
272
+ requireFile(filepath) {
273
+ const timingKey = `Require(${this[REQUIRE_COUNT]++}) ${utils.getResolvedFilename(filepath, this.options.baseDir)}`;
274
+ this.timing.start(timingKey);
275
+ const ret = utils.loadFile(filepath);
276
+ this.timing.end(timingKey);
277
+ return ret;
278
+ }
279
+
280
+ /**
281
+ * Get all loadUnit
282
+ *
283
+ * loadUnit is a directory that can be loaded by EeLoader, it has the same structure.
284
+ * loadUnit has a path and a type(app, framework, plugin).
285
+ *
286
+ * The order of the loadUnits:
287
+ *
288
+ * 1. plugin
289
+ * 2. framework
290
+ * 3. app
291
+ *
292
+ * @return {Array} loadUnits
293
+ * @since 1.0.0
294
+ */
295
+ getLoadUnits() {
296
+ if (this.dirs) {
297
+ return this.dirs;
298
+ }
299
+
300
+ const dirs = this.dirs = [];
301
+
302
+ if (this.orderPlugins) {
303
+ for (const plugin of this.orderPlugins) {
304
+ dirs.push({
305
+ path: plugin.path,
306
+ type: 'plugin',
307
+ });
308
+ }
309
+ }
310
+
311
+ // framework or Ee path
312
+ for (const EePath of this.EePaths) {
313
+ dirs.push({
314
+ path: EePath,
315
+ type: 'framework',
316
+ });
317
+ }
318
+
319
+ // application
320
+ dirs.push({
321
+ path: this.options.baseDir,
322
+ type: 'app',
323
+ });
324
+
325
+ debug('Loaded dirs %j', dirs);
326
+ return dirs;
327
+ }
328
+
329
+ /**
330
+ * Load files using {@link FileLoader}, inject to {@link Application}
331
+ * @param {String|Array} directory - see {@link FileLoader}
332
+ * @param {String} property - see {@link FileLoader}
333
+ * @param {Object} opt - see {@link FileLoader}
334
+ * @since 1.0.0
335
+ */
336
+ loadToApp(directory, property, opt) {
337
+ const target = this.app[property] = {};
338
+ opt = Object.assign({}, {
339
+ directory,
340
+ target,
341
+ inject: this.app,
342
+ }, opt);
343
+
344
+ const timingKey = `Load "${String(property)}" to Application`;
345
+ this.timing.start(timingKey);
346
+ new FileLoader(opt).load();
347
+ //console.log('app property:', this.app[property]);
348
+ this.timing.end(timingKey);
349
+ }
350
+
351
+ /**
352
+ * Load files using {@link ContextLoader}
353
+ * @param {String|Array} directory - see {@link ContextLoader}
354
+ * @param {String} property - see {@link ContextLoader}
355
+ * @param {Object} opt - see {@link ContextLoader}
356
+ * @since 1.0.0
357
+ */
358
+ loadToContext(directory, property, opt) {
359
+ opt = Object.assign({}, {
360
+ directory,
361
+ property,
362
+ inject: this.app,
363
+ }, opt);
364
+ const timingKey = `Load "${String(property)}" to Context`;
365
+ this.timing.start(timingKey);
366
+ new ContextLoader(opt).load();
367
+ this.timing.end(timingKey);
368
+ }
369
+
370
+ /**
371
+ * @member {FileLoader} EeLoader#FileLoader
372
+ * @since 1.0.0
373
+ */
374
+ get FileLoader() {
375
+ return FileLoader;
376
+ }
377
+
378
+ /**
379
+ * @member {ContextLoader} EeLoader#ContextLoader
380
+ * @since 1.0.0
381
+ */
382
+ get ContextLoader() {
383
+ return ContextLoader;
384
+ }
385
+
386
+ getTypeFiles(filename) {
387
+ const files = [ `${filename}.default` ];
388
+ if (this.serverScope) files.push(`${filename}.${this.serverScope}`);
389
+ if (this.serverEnv === 'default') return files;
390
+
391
+ files.push(`${filename}.${this.serverEnv}`);
392
+ if (this.serverScope) files.push(`${filename}.${this.serverScope}_${this.serverEnv}`);
393
+ return files;
394
+ }
395
+
396
+ resolveModule(filepath) {
397
+ let fullPath;
398
+ try {
399
+ fullPath = require.resolve(filepath);
400
+ } catch (e) {
401
+ return undefined;
402
+ }
403
+
404
+ if (process.env.Ee_TYPESCRIPT !== 'true' && fullPath.endsWith('.ts')) {
405
+ return undefined;
406
+ }
407
+
408
+ return fullPath;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Mixin methods to EeLoader
414
+ * // ES6 Multiple Inheritance
415
+ * https://medium.com/@leocavalcante/es6-multiple-inheritance-73a3c66d2b6b
416
+ */
417
+ const loaders = [
418
+ require('./mixin/config'),
419
+ require('./mixin/service'),
420
+ require('./mixin/controller'),
421
+ ];
422
+
423
+ for (const loader of loaders) {
424
+ Object.assign(EeLoader.prototype, loader);
425
+ }
426
+
427
+ module.exports = EeLoader;
@@ -0,0 +1,262 @@
1
+ 'use strict';
2
+
3
+ const assert = require('assert');
4
+ const fs = require('fs');
5
+ const debug = require('debug')('ee-core:fileLoader');
6
+ const path = require('path');
7
+ const globby = require('globby');
8
+ const is = require('is-type-of');
9
+ const deprecate = require('depd')('ee');
10
+ const utils = require('../utils');
11
+ const FULLPATH = Symbol('EE_LOADER_ITEM_FULLPATH');
12
+ const EXPORTS = Symbol('EE_LOADER_ITEM_EXPORTS');
13
+
14
+ const defaults = {
15
+ directory: null,
16
+ target: null,
17
+ match: undefined,
18
+ ignore: undefined,
19
+ lowercaseFirst: false,
20
+ caseStyle: 'camel',
21
+ initializer: null,
22
+ call: true,
23
+ override: false,
24
+ inject: undefined,
25
+ filter: null,
26
+ };
27
+
28
+ /**
29
+ * Load files from directory to target object.
30
+ * @since 1.0.0
31
+ */
32
+ class FileLoader {
33
+
34
+ /**
35
+ * @class
36
+ * @param {Object} options - options
37
+ * @param {String|Array} options.directory - directories to be loaded
38
+ * @param {Object} options.target - attach the target object from loaded files
39
+ * @param {String} options.match - match the files when load, support glob, default to all js files
40
+ * @param {String} options.ignore - ignore the files when load, support glob
41
+ * @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path`
42
+ * @param {Boolean} options.call - determine whether invoke when exports is function
43
+ * @param {Boolean} options.override - determine whether override the property when get the same name
44
+ * @param {Object} options.inject - an object that be the argument when invoke the function
45
+ * @param {Function} options.filter - a function that filter the exports which can be loaded
46
+ * @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.
47
+ */
48
+ constructor(options) {
49
+ assert(options.directory, 'options.directory is required');
50
+ assert(options.target, 'options.target is required');
51
+ this.options = Object.assign({}, defaults, options);
52
+
53
+ // compatible old options _lowercaseFirst_
54
+ if (this.options.lowercaseFirst === true) {
55
+ deprecate('lowercaseFirst is deprecated, use caseStyle instead');
56
+ this.options.caseStyle = 'lower';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * attach items to target object. Mapping the directory to properties.
62
+ * `app/controller/group/repository.js` => `target.group.repository`
63
+ * @return {Object} target
64
+ * @since 1.0.0
65
+ */
66
+ load() {
67
+ const items = this.parse();
68
+ //console.log('FileLoader load items:', items);
69
+ const target = this.options.target;
70
+ for (const item of items) {
71
+ // item { properties: [ 'a', 'b', 'c'], exports }
72
+ // => target.a.b.c = exports
73
+ item.properties.reduce((target, property, index) => {
74
+ let obj;
75
+ const properties = item.properties.slice(0, index + 1).join('.');
76
+ if (index === item.properties.length - 1) {
77
+ if (property in target) {
78
+ if (!this.options.override) throw new Error(`can't overwrite property '${properties}' from ${target[property][FULLPATH]} by ${item.fullpath}`);
79
+ }
80
+ obj = item.exports;
81
+ if (obj && !is.primitive(obj)) {
82
+ obj[FULLPATH] = item.fullpath;
83
+ obj[EXPORTS] = true;
84
+ }
85
+ } else {
86
+ obj = target[property] || {};
87
+ }
88
+
89
+ target[property] = obj;
90
+ debug('loaded %s', properties);
91
+ return obj;
92
+ }, target);
93
+ }
94
+
95
+ return target;
96
+ }
97
+
98
+ /**
99
+ * Parse files from given directories, then return an items list, each item contains properties and exports.
100
+ *
101
+ * For example, parse `app/controller/group/repository.js`
102
+ *
103
+ * ```
104
+ * module.exports = app => {
105
+ * return class RepositoryController extends app.Controller {};
106
+ * }
107
+ * ```
108
+ *
109
+ * It returns a item
110
+ *
111
+ * ```
112
+ * {
113
+ * properties: [ 'group', 'repository' ],
114
+ * exports: app => { ... },
115
+ * }
116
+ * ```
117
+ *
118
+ * `Properties` is an array that contains the directory of a filepath.
119
+ *
120
+ * `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.
121
+ * @return {Array} items
122
+ * @since 1.0.0
123
+ */
124
+ parse() {
125
+ let files = this.options.match;
126
+
127
+ if (!files) {
128
+ files = (process.env.EE_TYPESCRIPT === 'true' && utils.extensions['.ts'])
129
+ ? [ '**/*.(js|ts)', '!**/*.d.ts' ]
130
+ : [ '**/*.js' ];
131
+ } else {
132
+ files = Array.isArray(files) ? files : [ files ];
133
+ }
134
+
135
+ let ignore = this.options.ignore;
136
+ if (ignore) {
137
+ ignore = Array.isArray(ignore) ? ignore : [ ignore ];
138
+ ignore = ignore.filter(f => !!f).map(f => '!' + f);
139
+ files = files.concat(ignore);
140
+ }
141
+
142
+ let directories = this.options.directory;
143
+ if (!Array.isArray(directories)) {
144
+ directories = [ directories ];
145
+ }
146
+
147
+ const filter = is.function(this.options.filter) ? this.options.filter : null;
148
+ const items = [];
149
+ debug('parsing %j', directories);
150
+
151
+ for (const directory of directories) {
152
+ const filepaths = globby.sync(files, { cwd: directory });
153
+
154
+ for (const filepath of filepaths) {
155
+ const fullpath = path.join(directory, filepath);
156
+ if (!fs.statSync(fullpath).isFile()) continue;
157
+ // get properties
158
+ // app/service/foo/bar.js => [ 'foo', 'bar' ]
159
+ const properties = getProperties(filepath, this.options);
160
+ // app/service/foo/bar.js => service.foo.bar
161
+ const pathName = directory.split(/[/\\]/).slice(-1) + '.' + properties.join('.');
162
+ // get exports from the file
163
+ const exports = getExports(fullpath, this.options, pathName);
164
+
165
+ // ignore exports when it's null or false returned by filter function
166
+ if (exports == null || (filter && filter(exports) === false)) continue;
167
+
168
+ // set properties of class
169
+ if (is.class(exports)) {
170
+ exports.prototype.pathName = pathName;
171
+ exports.prototype.fullPath = fullpath;
172
+ }
173
+ //console.log('lll---------------pathName:', pathName);
174
+ items.push({ fullpath, properties, exports });
175
+ debug('parse %s, properties %j, export %O', fullpath, properties, exports);
176
+ }
177
+ }
178
+
179
+ return items;
180
+ }
181
+
182
+ }
183
+
184
+ module.exports = FileLoader;
185
+ module.exports.EXPORTS = EXPORTS;
186
+ module.exports.FULLPATH = FULLPATH;
187
+
188
+ // convert file path to an array of properties
189
+ // a/b/c.js => ['a', 'b', 'c']
190
+ function getProperties(filepath, { caseStyle }) {
191
+ // if caseStyle is function, return the result of function
192
+ if (is.function(caseStyle)) {
193
+ const result = caseStyle(filepath);
194
+ assert(is.array(result), `caseStyle expect an array, but got ${result}`);
195
+ return result;
196
+ }
197
+ // use default camelize
198
+ return defaultCamelize(filepath, caseStyle);
199
+ }
200
+
201
+ // Get exports from filepath
202
+ // If exports is null/undefined, it will be ignored
203
+ function getExports(fullpath, { initializer, call, inject }, pathName) {
204
+ let exports = utils.loadFile(fullpath);
205
+ // process exports as you like
206
+ if (initializer) {
207
+ exports = initializer(exports, { path: fullpath, pathName });
208
+ }
209
+
210
+ // return exports when it's a class or generator
211
+ //
212
+ // module.exports = class Service {};
213
+ // or
214
+ // module.exports = function*() {}
215
+ if (is.class(exports) || is.generatorFunction(exports) || is.asyncFunction(exports)) {
216
+ return exports;
217
+ }
218
+
219
+ // return exports after call when it's a function
220
+ //
221
+ // module.exports = function(app) {
222
+ // return {};
223
+ // }
224
+ if (call && is.function(exports)) {
225
+ exports = exports(inject);
226
+ if (exports != null) {
227
+ return exports;
228
+ }
229
+ }
230
+
231
+ // return exports what is
232
+ return exports;
233
+ }
234
+
235
+ function defaultCamelize(filepath, caseStyle) {
236
+ const properties = filepath.substring(0, filepath.lastIndexOf('.')).split('/');
237
+ return properties.map(property => {
238
+ if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
239
+ throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);
240
+ }
241
+
242
+ // use default camelize, will capitalize the first letter
243
+ // foo_bar.js > FooBar
244
+ // fooBar.js > FooBar
245
+ // FooBar.js > FooBar
246
+ // FooBar.js > FooBar
247
+ // FooBar.js > fooBar (if lowercaseFirst is true)
248
+ property = property.replace(/[_-][a-z]/ig, s => s.substring(1).toUpperCase());
249
+ let first = property[0];
250
+ switch (caseStyle) {
251
+ case 'lower':
252
+ first = first.toLowerCase();
253
+ break;
254
+ case 'upper':
255
+ first = first.toUpperCase();
256
+ break;
257
+ case 'camel':
258
+ default:
259
+ }
260
+ return first + property.substring(1);
261
+ });
262
+ }