1
1
/**
2
2
* Name: index.ts
3
- * Description: Plugin manager and validator
3
+ * Description: Plugin loader, manager and validator
4
4
* Author: Ovidiu Barabula <lectii2008@gmail.com>
5
5
* @since 0.1.0
6
6
*/
7
7
8
+ import * as path from 'path' ;
8
9
import { IConfigWizard , QuestionnaireSubscriber } from '../config-wizard' ;
9
10
import { TaskManager , TaskSubscriber } from '../task-manager' ;
10
11
import Logger , { ILogger } from '../util/logger' ;
12
+ import { dynamicRequire , flattenArray , pluginName } from '../util/utility-functions' ;
11
13
import Installable , { InstallableObject } from './installable' ;
12
14
13
15
export interface Plugin {
@@ -17,17 +19,23 @@ export interface Plugin {
17
19
}
18
20
19
21
export interface PluginManager {
20
- use ( plugin : Plugin | InstallableObject ) : Promise < void > ;
21
- validate ?( plugin : Plugin ) : boolean ;
22
+ use ( ...plugin : Array < string | Plugin | InstallableObject > ) : Promise < void > ;
23
+ /* test:start */
24
+ loadPlugin ?( name : string ) : any ;
25
+ parsePlugins ?( plugins : PluginsArray ) : Promise < Plugin [ ] > ;
26
+ /* test:end */
22
27
}
23
28
29
+ export type PluginsArray = Array < string | Plugin | InstallableObject > ;
24
30
export type PluginSubscribers = TaskSubscriber | QuestionnaireSubscriber ;
25
31
26
32
27
33
// Custom error messages
28
34
export const ERRORS = {
29
35
NO_CONFIG_WIZARD : 'PluginManager() requires second argument to be a ConfigWizard instance' ,
30
36
NO_TASK_MANAGER : 'PluginManager() requires first argument to be a TaskManager instance' ,
37
+ PLUGIN_NAME_SHOULD_BE_STRING : 'PluginManager() passed in plugin name should be a string' ,
38
+ PLUGIN_NOT_FOUND : 'PluginManager> plugin could not be loaded' ,
31
39
} ;
32
40
33
41
@@ -56,6 +64,57 @@ function PluginManager(
56
64
}
57
65
58
66
67
+ /**
68
+ * Parse list of plugins and return installable plugin objects
69
+ * @param plugins Array of plugins, plugin names or installable objects
70
+ */
71
+ async function parsePlugins ( plugins : PluginsArray ) : Promise < Plugin [ ] > {
72
+ return await Promise . all (
73
+ plugins . reduce ( ( pluginsArray : PluginsArray , item ) => {
74
+ let plugin : PluginsArray | Plugin | InstallableObject ;
75
+
76
+ // Try to load the plugin if current item is a plugin name
77
+ if ( typeof item === 'string' ) {
78
+ try {
79
+ plugin = loadPlugin ( item ) ;
80
+ } catch ( error ) {
81
+ // Log the error and return the array without the bad plugin
82
+ logger . error ( error . message ) ;
83
+ return pluginsArray ;
84
+ }
85
+ // If it's not a string, then it must be a Plugin or InstallableObject
86
+ } else {
87
+ plugin = item ;
88
+ }
89
+
90
+ // Flatten everything so we have a one dimentional array
91
+ // Loaded plugins could be arrays of installable objects
92
+ return flattenArray ( [ pluginsArray , plugin ] ) ;
93
+ } , [ ] )
94
+
95
+ // Convert all to installable plugins
96
+ . map ( ( plugin : Plugin | InstallableObject ) => Installable ( plugin ) ) ,
97
+ ) ;
98
+ }
99
+
100
+
101
+ /**
102
+ * Get plugin from node_modules and return it
103
+ * @param name Plugin name
104
+ */
105
+ function loadPlugin ( name : string ) : any {
106
+ if ( typeof name !== 'string' ) {
107
+ throw new Error ( ERRORS . PLUGIN_NAME_SHOULD_BE_STRING ) ;
108
+ }
109
+
110
+ try {
111
+ return dynamicRequire ( pluginName ( name ) ) ;
112
+ } catch ( error ) {
113
+ throw new Error ( `${ ERRORS . PLUGIN_NOT_FOUND } : ${ error . message } ` ) ;
114
+ }
115
+ }
116
+
117
+
59
118
/**
60
119
* Provides an array of plugin subscribers
61
120
*/
@@ -70,27 +129,44 @@ function PluginManager(
70
129
71
130
72
131
/**
73
- * Register plugin
74
- * @param plugin Plugin object
132
+ * Convert to installable plugin and call the plugin .install() method
133
+ * @param plugin Plugin or Installable object
75
134
*/
76
- async function use ( plugin : Plugin | InstallableObject ) : Promise < void > {
77
- // TODO: Add support for plugin as string
78
- // TODO: When plugin is of type string, look for plugin in node_modules
79
-
135
+ async function install ( plugin : Plugin ) : Promise < void > {
136
+ // Get the array of plugin subscribers (tasks, questionnaires, etc.)
80
137
const subscribers : PluginSubscribers [ ] = getPluginSubscribers ( ) ;
138
+ // Call the plugin's .install() method and provide plugin subscriber objects
139
+ await plugin . install ( ...subscribers ) ;
140
+ }
81
141
82
- try {
83
- await Installable ( plugin ) . install ( ...subscribers ) ;
84
- } catch ( error ) {
85
- logger . error ( error . message ) ;
86
- }
142
+
143
+ /**
144
+ * Register plugin(s)
145
+ * @param plugins Plugin object(s)
146
+ */
147
+ async function use ( ...plugins : PluginsArray ) : Promise < void > {
148
+ // Parse plugins array and get installable plugins
149
+ const installablePlugins = await parsePlugins ( plugins ) ;
150
+ // Install each plugin
151
+ await installablePlugins . map ( async ( plugin : Plugin ) => await install ( plugin ) ) ;
87
152
}
88
153
89
154
90
- // Return public API
91
- return Object . freeze ( {
155
+ // Public API
156
+ let publicApi : PluginManager = {
92
157
use,
93
- } ) ;
158
+ } ;
159
+
160
+ /* test:start */
161
+ // Add private methods to API, for testing only
162
+ publicApi = { ...publicApi ,
163
+ loadPlugin,
164
+ parsePlugins,
165
+ } ;
166
+ /* test:end */
167
+
168
+ // Return public API
169
+ return Object . freeze ( publicApi ) ;
94
170
}
95
171
96
172
export default PluginManager ;
0 commit comments