1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | const { readJson, statSync } = require ('fs-extra');
|
18 | const path = require ('path');
|
19 | const assert = require ('assert');
|
20 | const { BO } = require ('base-object');
|
21 | const ApplicationModule = require ('./application-module');
|
22 | const Events = require ('./messaging/events');
|
23 |
|
24 | const { forEach, find } = require ('lodash');
|
25 | const debug = require ('debug') ('blueprint:module-loader');
|
26 |
|
27 | const KEYWORD_BLUEPRINT_MODULE = 'blueprint-module';
|
28 | const FILE_PACKAGE_JSON = 'package.json';
|
29 |
|
30 | function isBlueprintModule (packageObj) {
|
31 | return packageObj.keywords && packageObj.keywords.indexOf (KEYWORD_BLUEPRINT_MODULE) !== -1;
|
32 | }
|
33 |
|
34 | module.exports = BO.extend (Events, {
|
35 |
|
36 | app: null,
|
37 |
|
38 |
|
39 | _modules: null,
|
40 |
|
41 | init () {
|
42 | this._super.call (this, ...arguments);
|
43 | this._modules = {};
|
44 |
|
45 | assert (!!this.app, 'You must define the app property');
|
46 | },
|
47 |
|
48 | load () {
|
49 | let packageFile = path.resolve (this.app.appPath, '..', FILE_PACKAGE_JSON);
|
50 |
|
51 | return readJson (packageFile).then (packageObj => {
|
52 | if (packageObj || packageObj.dependencies)
|
53 | return this._handleDependencies (packageObj.dependencies);
|
54 | });
|
55 | },
|
56 |
|
57 | _handleDependencies (dependencies) {
|
58 | let promises = [];
|
59 |
|
60 | forEach (dependencies, (version, name) => promises.push (this._handleNodeModule (name, version)));
|
61 |
|
62 | return Promise.all (promises);
|
63 | },
|
64 |
|
65 | _handleNodeModule (name, version) {
|
66 |
|
67 | if (this._modules[name])
|
68 | return;
|
69 |
|
70 |
|
71 |
|
72 | let modulePath;
|
73 |
|
74 | if (version.startsWith ('file:')) {
|
75 |
|
76 | let relativePath = version.slice (5);
|
77 | modulePath = path.resolve (this.app.appPath, '..', relativePath);
|
78 | }
|
79 | else {
|
80 | modulePath = this._resolveModulePath (name);
|
81 | }
|
82 |
|
83 | const packageFile = path.resolve (modulePath, FILE_PACKAGE_JSON);
|
84 |
|
85 | return readJson (packageFile).then (packageObj => {
|
86 |
|
87 |
|
88 | if (!isBlueprintModule (packageObj))
|
89 | return;
|
90 |
|
91 | debug (`loading module ${name}`);
|
92 |
|
93 |
|
94 | const moduleAppPath = path.resolve (modulePath, 'app');
|
95 | const module = new ApplicationModule ({name, app: this.app, modulePath: moduleAppPath});
|
96 |
|
97 | this._modules[name] = module;
|
98 |
|
99 |
|
100 |
|
101 | const {dependencies} = packageObj;
|
102 |
|
103 | return this._handleDependencies (dependencies)
|
104 | .then (() => this.emit ('loading', module))
|
105 | .then (() => module.configure ())
|
106 | .then (module => this.emit ('loaded', module))
|
107 | });
|
108 | },
|
109 |
|
110 | _resolveModulePath (name) {
|
111 |
|
112 |
|
113 | const paths = [path.resolve (this.app.appPath, '../node_modules'), ...module.paths];
|
114 |
|
115 | let basename = find (paths, basename => {
|
116 | let modulePath = path.resolve (basename, name, 'package.json');
|
117 |
|
118 | try {
|
119 | return statSync (modulePath).isFile ();
|
120 | }
|
121 | catch (err) {
|
122 | return false;
|
123 | }
|
124 | });
|
125 |
|
126 | if (!basename)
|
127 | throw new Error (`Cannot locate modules for ${name}`);
|
128 |
|
129 | return path.resolve (basename, name);
|
130 | }
|
131 | });
|