-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathConstructionStrategyCommonJs.ts
More file actions
128 lines (113 loc) · 4.62 KB
/
ConstructionStrategyCommonJs.ts
File metadata and controls
128 lines (113 loc) · 4.62 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
import * as Path from 'path';
import type { IModuleState } from '../../loading/ModuleStateBuilder';
import type {
ICreationStrategyInstanceOptions,
IConstructionStrategy,
ICreationStrategyHashOptions,
ICreationStrategyArrayOptions,
ICreationStrategySupplierOptions,
ICreationStrategyPrimitiveOptions, ICreationStrategyVariableOptions,
} from './IConstructionStrategy';
/**
* A creation strategy for creating instances with CommonJS.
*/
export class ConstructionStrategyCommonJs implements IConstructionStrategy<any> {
private readonly overrideRequireNames: Record<string, string>;
private readonly req: NodeJS.Require;
// eslint-disable-next-line unicorn/no-object-as-default-parameter
public constructor(options: ICreationStrategyCommonJsOptions = { req: require }) {
this.overrideRequireNames = options.overrideRequireNames || {};
this.req = options.req;
}
public createInstance(options: ICreationStrategyInstanceOptions<any>): any {
// Call require()
options.requireName = this.overrideRequireNames[options.requireName] || options.requireName;
// First try requiring current module, and fallback to a plain require
let object: any;
const currentResult = this.requireCurrentRunningModuleIfCurrent(options.moduleState, options.requireName);
object = currentResult !== false ?
currentResult.value :
this.req(options.requireName.startsWith('.') ?
Path.join(process.cwd(), options.requireName) :
this.req.resolve(options.requireName, { paths: [ options.moduleState.mainModulePath ]}));
// Determine the child of the require'd element
let subObject;
if (options.requireElement) {
const requireElementPath = options.requireElement.split('.');
try {
subObject = requireElementPath.reduce((acc: any, subRequireElement: string) => acc[subRequireElement], object);
} catch {
throw new Error(`Failed to get module element ${options.requireElement} from module ${options.requireName}`);
}
} else {
subObject = object;
}
if (!subObject) {
throw new Error(`Failed to get module element ${options.requireElement} from module ${options.requireName}`);
}
// Call the constructor of the element
object = subObject;
if (options.callConstructor) {
if (typeof object !== 'function') {
throw new Error(`Attempted to construct ${options.requireElement} from module ${options.requireName} that does not have a constructor`);
}
object = new (Function.prototype.bind.apply(object, <[any, ...any]>[{}, ...options.args ]))();
}
return object;
}
/**
* Require the given module iff the module is the current main module.
* This is done by looking for the nearest package.json.
* @param moduleState The module state.
* @param requireName The module name that should be required.
* @returns {any} The require() result
*/
public requireCurrentRunningModuleIfCurrent(moduleState: IModuleState, requireName: string): { value: any } | false {
const pckg = moduleState.packageJsons[moduleState.mainModulePath];
if (pckg && requireName === pckg.name) {
const mainPath: string = Path.posix.join(moduleState.mainModulePath, pckg.main);
const required = this.req(mainPath);
if (required) {
return { value: required };
}
}
return false;
}
public createHash(options: ICreationStrategyHashOptions<any>): any {
return Object.fromEntries(
options.entries
.filter((entry): entry is { key: string; value: any } => entry !== undefined)
.map(entry => [ entry.key, entry.value ]),
);
}
public createArray(options: ICreationStrategyArrayOptions<any>): any {
return options.elements;
}
public async createLazySupplier(options: ICreationStrategySupplierOptions<any>): Promise<any> {
return options.supplier;
}
public createPrimitive(options: ICreationStrategyPrimitiveOptions<any>): any {
return options.value;
}
public getVariableValue(options: ICreationStrategyVariableOptions<any>): any {
const value = options.settings.variables ? options.settings.variables[options.variableName] : undefined;
if (value === undefined) {
throw new Error(`Undefined variable: ${options.variableName}`);
}
return value;
}
public createUndefined(): any {
// Return undefined
}
}
export interface ICreationStrategyCommonJsOptions {
/**
* Overrides for `require()` calls.
* For example, an override entry `abc -> def` will map all calls from `require('abc')` to `require('def')`.
*/
overrideRequireNames?: Record<string, string>;
/**
* The `require` instance.
*/
req: NodeJS.Require;
}