UNPKG

4.23 kBJavaScriptView Raw
1/*
2 * Copyright (c) 2018 One Hill Technologies, LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17const { readJson, statSync } = require ('fs-extra');
18const path = require ('path');
19const assert = require ('assert');
20const { BO } = require ('base-object');
21const ApplicationModule = require ('./application-module');
22const Events = require ('./messaging/events');
23
24const { forEach, find } = require ('lodash');
25const debug = require ('debug') ('blueprint:module-loader');
26
27const KEYWORD_BLUEPRINT_MODULE = 'blueprint-module';
28const FILE_PACKAGE_JSON = 'package.json';
29
30function isBlueprintModule (packageObj) {
31 return packageObj.keywords && packageObj.keywords.indexOf (KEYWORD_BLUEPRINT_MODULE) !== -1;
32}
33
34module.exports = BO.extend (Events, {
35 /// The target application for the loader.
36 app: null,
37
38 /// Collection of loaded modules.
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 // Do not process the module more than once.
67 if (this._modules[name])
68 return;
69
70 // Open the package.json file for this node module, and determine
71 // if the module is a Blueprint.js module.
72 let modulePath;
73
74 if (version.startsWith ('file:')) {
75 // The file location is relative to the blueprint application.
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 // Do not continue if the module is not a Blueprint module, or we have
87 // already loaded this module into memory.
88 if (!isBlueprintModule (packageObj))
89 return;
90
91 debug (`loading module ${name}`);
92
93 // Create a new application module.
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 // Load the dependencies for this module, then configure this module, and
100 // then add this module to the application.
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 // Let's make sure the node_modules for the application appear on the path. This is
112 // important for examples applications that reside within an existing application
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});