-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathauto-loader.js
More file actions
executable file
·287 lines (254 loc) · 9.12 KB
/
auto-loader.js
File metadata and controls
executable file
·287 lines (254 loc) · 9.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
const FS = require('fs');
const Path = require('path');
// Fallback: Should always be CJS unless the user altered this file.
let INTERNAL_TYPE = 'CJS';
try {
let CommonJSCheck = require.main;
CommonJSCheck = module;
} catch (e) {
INTERNAL_TYPE = 'MJS';
}
/**
* Will attempt to auto load every JavaScript file in a requested directory.
*
* @param {String} path The path to the directory to attempt to auto load.
*/
const AutoLoader = (function (path) {
let TYPE;
let DIR;
const ALLOW = ['.js', '.cjs', '.mjs'];
/**
* Converts an absolute file path into an OS safe file:// path.
*
* @param {String} absPath
* @return {String} The path converted to a file:// path for Windows.
*/
const convertToFileURL = function (absPath) {
let pathName = Path.resolve(absPath).replace(/\\/g, '/');
// Windows drive letter must be prefixed with a slash.
if (pathName[0] !== '/') { pathName = `/${pathName}`; }
if (process.platform.includes('win')) {
return encodeURI(`file://${pathName}`).replace(':///', '://');
}
return pathName;
};
/**
* Get the array of allowed file types. Only files that end with these
* extensions will be auto loaded.
*
* @return {Array} An array of allowed file extensions.
*/
const getAllowed = function () {
return JSON.parse(JSON.stringify(ALLOW));
};
/**
* Quick and dirty way to detect if this package is being used in a
* CommonJS (CJS) or ES6 (MJS) environment.
*
* @return {String|null} The environment abbreviation or null if not found.
*/
const getCallerType = function () {
let s;
const error = new Error();
// eslint-disable-next-line no-cond-assign, no-void
const stack = (s = error.stack) === null || s === void 0 ? void 0 : s.split('\n');
const stackAsString = stack.toString();
const mjsCheck = new RegExp('modules\\/esm|Object\\.loadESM', 'g');
const cjsCheck = new RegExp('modules\\/cjs|Object\\.Module\\.', 'g');
if (mjsCheck.test(stackAsString)) {
return 'MJS';
}
if (cjsCheck.test(stackAsString)) {
return 'CJS';
}
return null;
};
/**
* The options argument for loadModules is optional. This function insures
* that all options are present or set to their defaults.
*
* @param {Object|undefined} options The user supplied options object or undefined.
* @return {Object} A complete options object for loadModules.
*/
const getCorrectOptions = function (options) {
if (!options || typeof options !== 'object') {
options = {
allowJSON: false,
checkFirst: null,
recursive: true
};
}
if (!options.allowJSON) {
options.allowJSON = false;
}
if (options.allowJSON) {
if (!ALLOW.includes('.json')) {
ALLOW.push('.json');
}
} else if (ALLOW.includes('.json')) {
ALLOW.splice(ALLOW.indexOf('.json'), 1);
}
if (!options.checkFirst || typeof options.checkFirst !== 'function') {
// Use an always passing checkFirst function.
options.checkFirst = (ignored) => true;
}
if (options.recursive === undefined || typeof options.recursive !== 'boolean') {
options.recursive = true;
}
return options;
};
/**
* Getter to retrieve the currently configured directory to auto load.
*
* @return {String} The currently configured directory to auto load.
*/
const getDirectory = function () {
return DIR;
};
/**
* Build an array of all modules requested to be auto loaded.
*
* @param {String} dir The directory to auto load.
* @param {Array} modules An array of absolute paths to the modules to load.
* @param {Object} options The options being used by loadModules.
* @return {Array} An array of absolute paths to the modules to auto loaded.
*/
const getModulePaths = function (dir, modules, options) {
if (!Array.isArray(modules)) {
options = modules;
modules = [];
}
const items = FS.readdirSync(dir);
for (let i = 0; i < items.length; i++) {
const pathToItem = Path.normalize(Path.join(dir, items[i]));
if (ALLOW.includes(Path.extname(pathToItem))) {
modules.push(pathToItem);
} else if (FS.lstatSync(pathToItem).isDirectory() && options.recursive) {
getModulePaths(pathToItem, modules, options);
}
}
return modules;
};
/**
* Get the type mode being used: CommonJS (CJS) or ES6 (MJS).
*
* @return {String} The type mode being used.
*/
const getType = function () {
if (!TYPE) {
setType();
}
return TYPE;
};
/**
* Auto load every JavaScript file located in the configured DIR and pass it back to a callback.
*
* @param {Function} callback A callback function to pass the loaded module to.
* @param {Object} options An optional object used to alter the way loadModules works:
* allowJSON: true if JSON files should be loaded too
* checkFirst: a function to path the modules abs path to, return true to load or false to skip
* recursive: true if all directories under DIR should be loaded too
* @return {Promise} Returns a promise that will resolve with a results object.
*/
const loadModules = async function (callback, options) {
options = getCorrectOptions(options);
if (!TYPE) {
setType();
}
if (!FS.existsSync(DIR)) {
throw new ReferenceError(`Directory does not exists: ${DIR}`);
}
if (typeof callback !== 'function') {
throw new ReferenceError('A callback function is required for `loadModules`.');
}
const results = {
failed: [],
failedCount: 0,
loaded: [],
loadedCount: 0
};
const modules = getModulePaths(DIR, options);
for (let i = 0; i < modules.length; i++) {
const load = modules[i];
const filePath = convertToFileURL(load);
try {
// Only load the file if the user wants it; allows skipping files.
if (options.checkFirst(filePath)) {
const ext = Path.extname(filePath);
let newModule;
if (ext === '.json') {
// eslint-disable-next-line global-require, import/no-dynamic-require
newModule = JSON.parse(FS.readFileSync(filePath, 'utf8'));
} else if (ext === '.mjs' || TYPE === 'MJS') {
// eslint-disable-next-line no-await-in-loop
newModule = await import(filePath);
} else if (ext === '.cjs' || TYPE === 'CJS') {
// eslint-disable-next-line global-require, import/no-dynamic-require
newModule = require(filePath);
}
if (newModule) {
callback(newModule);
}
results.loaded.push(filePath);
results.loadedCount += 1;
}
} catch (error) {
results.failed.push({
error: error.message,
file: filePath
});
results.failedCount += 1;
}
}
return results;
};
/**
* Set the type of module mode being used: CommonJS (CJS) or ES6 (MJS).
*
* @param {String} type CommonJS (CJS) or ES6 (MJS).
* @return {null} Returns nothing, used as a short circuit.
*/
const setType = function (type) {
// If a type was passed in attempt to use it.
if (type) {
type = type.toUpperCase().trim();
if (type === 'MJS' || type === 'CJS') {
TYPE = type;
return;
}
}
// If no type was passed in attempt to determine it.
TYPE = getCallerType();
if (!TYPE) {
TYPE = INTERNAL_TYPE;
}
};
/**
* Setter to change the configured DIR to auto load.
*
* @param {String} path The path to the directory to attempt to auto load.
*/
const setDirectory = function (pathToDir) {
pathToDir = pathToDir || __dirname;
if (pathToDir[0] === '.') {
if (module && module.parent) {
pathToDir = Path.join(module.parent.path, pathToDir);
} else {
pathToDir = Path.join(__dirname, pathToDir);
}
}
DIR = Path.resolve(pathToDir);
};
// Initialize the class when instantiated.
setDirectory(path);
// Public methods.
return {
getAllowed,
getDirectory,
getType,
loadModules,
setType,
setDirectory,
};
});
module.exports = AutoLoader;