@vitronai/themis 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/benchmark-gate.json +10 -0
- package/bin/themis.js +8 -0
- package/docs/api.md +210 -0
- package/docs/publish.md +55 -0
- package/docs/release-policy.md +54 -0
- package/docs/schemas/agent-result.v1.json +277 -0
- package/docs/schemas/failures.v1.json +78 -0
- package/docs/vscode-extension.md +40 -0
- package/docs/why-themis.md +111 -0
- package/globals.d.ts +22 -0
- package/globals.js +1 -0
- package/index.d.ts +190 -0
- package/index.js +17 -0
- package/package.json +90 -0
- package/src/artifacts.js +207 -0
- package/src/assets/themisBg.png +0 -0
- package/src/assets/themisLogo.png +0 -0
- package/src/assets/themisReport.png +0 -0
- package/src/cli.js +395 -0
- package/src/config.js +52 -0
- package/src/discovery.js +34 -0
- package/src/environment.js +108 -0
- package/src/expect.js +175 -0
- package/src/init.js +22 -0
- package/src/module-loader.js +489 -0
- package/src/reporter.js +2141 -0
- package/src/runner.js +168 -0
- package/src/runtime.js +472 -0
- package/src/snapshots.js +90 -0
- package/src/stability.js +98 -0
- package/src/test-utils.js +201 -0
- package/src/verdict.js +71 -0
- package/src/watch.js +154 -0
- package/src/worker.js +26 -0
package/src/expect.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const util = require('util');
|
|
2
|
+
const { isMockFunction, formatMockCalls } = require('./test-utils');
|
|
3
|
+
|
|
4
|
+
function createExpect(context = {}) {
|
|
5
|
+
return function expect(received) {
|
|
6
|
+
return {
|
|
7
|
+
toBe(expected) {
|
|
8
|
+
if (!Object.is(received, expected)) {
|
|
9
|
+
throw new Error(`Expected ${format(received)} to be ${format(expected)}`);
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
toEqual(expected) {
|
|
13
|
+
if (!util.isDeepStrictEqual(received, expected)) {
|
|
14
|
+
throw new Error(`Expected ${format(received)} to deeply equal ${format(expected)}`);
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
toMatchObject(expected) {
|
|
18
|
+
if (!matchesObjectShape(received, expected)) {
|
|
19
|
+
throw new Error(`Expected ${format(received)} to match object shape ${format(expected)}`);
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
toBeTruthy() {
|
|
23
|
+
if (!received) {
|
|
24
|
+
throw new Error(`Expected ${format(received)} to be truthy`);
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
toBeFalsy() {
|
|
28
|
+
if (received) {
|
|
29
|
+
throw new Error(`Expected ${format(received)} to be falsy`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
toBeDefined() {
|
|
33
|
+
if (received === undefined) {
|
|
34
|
+
throw new Error('Expected value to be defined');
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
toBeUndefined() {
|
|
38
|
+
if (received !== undefined) {
|
|
39
|
+
throw new Error(`Expected ${format(received)} to be undefined`);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
toBeNull() {
|
|
43
|
+
if (received !== null) {
|
|
44
|
+
throw new Error(`Expected ${format(received)} to be null`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
toHaveLength(expected) {
|
|
48
|
+
if (!received || typeof received.length !== 'number') {
|
|
49
|
+
throw new Error('toHaveLength expects a value with a length property');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (received.length !== expected) {
|
|
53
|
+
throw new Error(`Expected length ${received.length} to be ${expected}`);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
toContain(item) {
|
|
57
|
+
if (typeof received === 'string') {
|
|
58
|
+
if (!received.includes(item)) {
|
|
59
|
+
throw new Error(`Expected string ${format(received)} to contain ${format(item)}`);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(received)) {
|
|
65
|
+
if (!received.some((entry) => util.isDeepStrictEqual(entry, item))) {
|
|
66
|
+
throw new Error(`Expected array ${format(received)} to contain ${format(item)}`);
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw new Error('toContain only supports strings and arrays');
|
|
72
|
+
},
|
|
73
|
+
toThrow(match) {
|
|
74
|
+
if (typeof received !== 'function') {
|
|
75
|
+
throw new Error('toThrow expects a function');
|
|
76
|
+
}
|
|
77
|
+
let thrown = null;
|
|
78
|
+
try {
|
|
79
|
+
received();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
thrown = error;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!thrown) {
|
|
85
|
+
throw new Error('Expected function to throw');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (match === undefined) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const message = String(thrown.message || thrown);
|
|
93
|
+
if (typeof match === 'string' && !message.includes(match)) {
|
|
94
|
+
throw new Error(`Expected thrown message ${format(message)} to include ${format(match)}`);
|
|
95
|
+
}
|
|
96
|
+
if (match instanceof RegExp && !match.test(message)) {
|
|
97
|
+
throw new Error(`Expected thrown message ${format(message)} to match ${String(match)}`);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
toHaveBeenCalled() {
|
|
101
|
+
assertMockFunction(received, 'toHaveBeenCalled');
|
|
102
|
+
if (received.mock.calls.length === 0) {
|
|
103
|
+
throw new Error('Expected mock function to have been called');
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
toHaveBeenCalledTimes(expected) {
|
|
107
|
+
assertMockFunction(received, 'toHaveBeenCalledTimes');
|
|
108
|
+
if (received.mock.calls.length !== expected) {
|
|
109
|
+
throw new Error(`Expected mock function to be called ${expected} times, received ${received.mock.calls.length}`);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
toHaveBeenCalledWith(...expectedArgs) {
|
|
113
|
+
assertMockFunction(received, 'toHaveBeenCalledWith');
|
|
114
|
+
if (!received.mock.calls.some((args) => util.isDeepStrictEqual(args, expectedArgs))) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Expected mock function to have been called with ${format(expectedArgs)}\nReceived calls: ${formatMockCalls(received)}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
toMatchSnapshot(snapshotName) {
|
|
121
|
+
if (!context.snapshotState) {
|
|
122
|
+
throw new Error('toMatchSnapshot() is only available inside the Themis runtime');
|
|
123
|
+
}
|
|
124
|
+
context.snapshotState.matchSnapshot(received, snapshotName);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assertMockFunction(received, matcherName) {
|
|
131
|
+
if (!isMockFunction(received)) {
|
|
132
|
+
throw new Error(`${matcherName} expects a mock function`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function matchesObjectShape(received, expected) {
|
|
137
|
+
if (!received || typeof received !== 'object' || Array.isArray(received)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!expected || typeof expected !== 'object' || Array.isArray(expected)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
146
|
+
if (!Object.prototype.hasOwnProperty.call(received, key)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const receivedValue = received[key];
|
|
151
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
152
|
+
if (!matchesObjectShape(receivedValue, value)) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (!util.isDeepStrictEqual(receivedValue, value)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function format(value) {
|
|
167
|
+
return util.inspect(value, { depth: 5, colors: false, maxArrayLength: 20 });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const expect = createExpect();
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
createExpect,
|
|
174
|
+
expect
|
|
175
|
+
};
|
package/src/init.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { initConfig } = require('./config');
|
|
4
|
+
|
|
5
|
+
function runInit(cwd) {
|
|
6
|
+
initConfig(cwd);
|
|
7
|
+
|
|
8
|
+
const testsDir = path.join(cwd, 'tests');
|
|
9
|
+
if (!fs.existsSync(testsDir)) {
|
|
10
|
+
fs.mkdirSync(testsDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const sample = path.join(testsDir, 'example.test.js');
|
|
14
|
+
if (!fs.existsSync(sample)) {
|
|
15
|
+
const content = `describe('math', () => {\n test('adds numbers', () => {\n expect(1 + 1).toBe(2);\n });\n});\n`;
|
|
16
|
+
fs.writeFileSync(sample, content, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
runInit
|
|
22
|
+
};
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const Module = require('module');
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_SOURCE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx'];
|
|
6
|
+
const RESOLVABLE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.json'];
|
|
7
|
+
const DEFAULT_TS_COMPILER_OPTIONS = {
|
|
8
|
+
target: 'ES2020',
|
|
9
|
+
module: 'CommonJS',
|
|
10
|
+
moduleResolution: 'Node',
|
|
11
|
+
esModuleInterop: true
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function createModuleLoader(options = {}) {
|
|
15
|
+
const projectRoot = safeRealpath(path.resolve(options.cwd || process.cwd()));
|
|
16
|
+
const tsconfigPath = options.tsconfigPath === null
|
|
17
|
+
? null
|
|
18
|
+
: resolveTsconfigPath(projectRoot, options.tsconfigPath);
|
|
19
|
+
const packageTypeCache = new Map();
|
|
20
|
+
const loadedProjectModules = new Set();
|
|
21
|
+
const compilerState = {
|
|
22
|
+
context: null
|
|
23
|
+
};
|
|
24
|
+
const compilerContext = tsconfigPath && fs.existsSync(tsconfigPath)
|
|
25
|
+
? getCompilerContext(compilerState, projectRoot, tsconfigPath)
|
|
26
|
+
: null;
|
|
27
|
+
const originalResolveFilename = Module._resolveFilename;
|
|
28
|
+
const originalLoad = Module._load;
|
|
29
|
+
const originalLoaders = new Map();
|
|
30
|
+
const mockRegistry = new Map();
|
|
31
|
+
|
|
32
|
+
for (const extension of SUPPORTED_SOURCE_EXTENSIONS) {
|
|
33
|
+
originalLoaders.set(extension, require.extensions[extension] || null);
|
|
34
|
+
require.extensions[extension] = function themisSourceLoader(testModule, filename) {
|
|
35
|
+
if (!shouldHandleProjectFile(filename, projectRoot)) {
|
|
36
|
+
return delegateToOriginalLoader(originalLoaders, extension, testModule, filename);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
loadedProjectModules.add(filename);
|
|
40
|
+
|
|
41
|
+
if (!shouldTranspileFile(filename, projectRoot, packageTypeCache)) {
|
|
42
|
+
return delegateToOriginalLoader(originalLoaders, extension, testModule, filename);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
46
|
+
const compiled = transpileSource({
|
|
47
|
+
source,
|
|
48
|
+
filename,
|
|
49
|
+
compilerContext: compilerContext || getCompilerContext(compilerState, projectRoot, tsconfigPath)
|
|
50
|
+
});
|
|
51
|
+
testModule._compile(compiled, filename);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Module._resolveFilename = function themisResolveFilename(request, parent, isMain, resolutionOptions) {
|
|
56
|
+
const aliasedRequest = resolveConfiguredRequest({
|
|
57
|
+
request,
|
|
58
|
+
parentFile: parent && parent.filename,
|
|
59
|
+
projectRoot,
|
|
60
|
+
compilerContext
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return originalResolveFilename.call(
|
|
64
|
+
this,
|
|
65
|
+
aliasedRequest || request,
|
|
66
|
+
parent,
|
|
67
|
+
isMain,
|
|
68
|
+
resolutionOptions
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
Module._load = function themisModuleLoad(request, parent, isMain) {
|
|
73
|
+
const resolvedRequest = resolveRequestValue({
|
|
74
|
+
request,
|
|
75
|
+
parentFile: parent && parent.filename,
|
|
76
|
+
projectRoot,
|
|
77
|
+
compilerContext,
|
|
78
|
+
originalResolveFilename,
|
|
79
|
+
isMain
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (mockRegistry.has(resolvedRequest)) {
|
|
83
|
+
return materializeMock(mockRegistry.get(resolvedRequest));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return originalLoad.call(this, resolvedRequest, parent, isMain);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
loadFile(filePath) {
|
|
91
|
+
const resolvedPath = path.resolve(filePath);
|
|
92
|
+
const realPath = safeRealpath(resolvedPath);
|
|
93
|
+
delete require.cache[resolvedPath];
|
|
94
|
+
delete require.cache[realPath];
|
|
95
|
+
return require(realPath);
|
|
96
|
+
},
|
|
97
|
+
restore() {
|
|
98
|
+
Module._resolveFilename = originalResolveFilename;
|
|
99
|
+
Module._load = originalLoad;
|
|
100
|
+
|
|
101
|
+
for (const [extension, loader] of originalLoaders.entries()) {
|
|
102
|
+
if (loader) {
|
|
103
|
+
require.extensions[extension] = loader;
|
|
104
|
+
} else {
|
|
105
|
+
delete require.extensions[extension];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const filename of loadedProjectModules) {
|
|
110
|
+
delete require.cache[filename];
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
resolveRequest(request, parentFile) {
|
|
114
|
+
return resolveRequestValue({
|
|
115
|
+
request,
|
|
116
|
+
parentFile,
|
|
117
|
+
projectRoot,
|
|
118
|
+
compilerContext,
|
|
119
|
+
originalResolveFilename
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
registerMock(request, parentFile, factoryOrExports) {
|
|
123
|
+
const resolvedRequest = this.resolveRequest(request, parentFile);
|
|
124
|
+
mockRegistry.set(resolvedRequest, {
|
|
125
|
+
factory: factoryOrExports,
|
|
126
|
+
initialized: false,
|
|
127
|
+
value: undefined
|
|
128
|
+
});
|
|
129
|
+
delete require.cache[resolvedRequest];
|
|
130
|
+
return resolvedRequest;
|
|
131
|
+
},
|
|
132
|
+
unregisterMock(request, parentFile) {
|
|
133
|
+
const resolvedRequest = this.resolveRequest(request, parentFile);
|
|
134
|
+
mockRegistry.delete(resolvedRequest);
|
|
135
|
+
delete require.cache[resolvedRequest];
|
|
136
|
+
return resolvedRequest;
|
|
137
|
+
},
|
|
138
|
+
clearModuleMocks() {
|
|
139
|
+
for (const resolvedRequest of mockRegistry.keys()) {
|
|
140
|
+
delete require.cache[resolvedRequest];
|
|
141
|
+
}
|
|
142
|
+
mockRegistry.clear();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function safeRealpath(targetPath) {
|
|
148
|
+
try {
|
|
149
|
+
return fs.realpathSync.native(targetPath);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return targetPath;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function resolveRequestValue({
|
|
156
|
+
request,
|
|
157
|
+
parentFile,
|
|
158
|
+
projectRoot,
|
|
159
|
+
compilerContext,
|
|
160
|
+
originalResolveFilename,
|
|
161
|
+
isMain = false
|
|
162
|
+
}) {
|
|
163
|
+
const normalizedParent = parentFile ? safeRealpath(path.resolve(parentFile)) : path.join(projectRoot, '__themis_entry__.js');
|
|
164
|
+
const parentModule = {
|
|
165
|
+
id: normalizedParent,
|
|
166
|
+
filename: normalizedParent,
|
|
167
|
+
paths: Module._nodeModulePaths(path.dirname(normalizedParent))
|
|
168
|
+
};
|
|
169
|
+
const aliasedRequest = resolveConfiguredRequest({
|
|
170
|
+
request,
|
|
171
|
+
parentFile: normalizedParent,
|
|
172
|
+
projectRoot,
|
|
173
|
+
compilerContext
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return originalResolveFilename.call(
|
|
177
|
+
Module,
|
|
178
|
+
aliasedRequest || request,
|
|
179
|
+
parentModule,
|
|
180
|
+
isMain
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function materializeMock(mockEntry) {
|
|
185
|
+
if (!mockEntry.initialized) {
|
|
186
|
+
mockEntry.value = typeof mockEntry.factory === 'function'
|
|
187
|
+
? mockEntry.factory()
|
|
188
|
+
: mockEntry.factory;
|
|
189
|
+
mockEntry.initialized = true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return mockEntry.value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function resolveTsconfigPath(projectRoot, configuredPath) {
|
|
196
|
+
if (typeof configuredPath === 'string' && configuredPath.trim().length > 0) {
|
|
197
|
+
return path.resolve(projectRoot, configuredPath);
|
|
198
|
+
}
|
|
199
|
+
return path.join(projectRoot, 'tsconfig.json');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function shouldHandleProjectFile(filename, projectRoot) {
|
|
203
|
+
const relativePath = path.relative(projectRoot, filename);
|
|
204
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return !relativePath.split(path.sep).includes('node_modules');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function shouldTranspileFile(filename, projectRoot, packageTypeCache) {
|
|
211
|
+
const extension = path.extname(filename).toLowerCase();
|
|
212
|
+
|
|
213
|
+
if (extension === '.ts' || extension === '.tsx' || extension === '.jsx') {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (extension !== '.js') {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return findNearestPackageType(filename, projectRoot, packageTypeCache) === 'module';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function delegateToOriginalLoader(originalLoaders, extension, testModule, filename) {
|
|
225
|
+
const loader = originalLoaders.get(extension) || originalLoaders.get('.js');
|
|
226
|
+
if (!loader) {
|
|
227
|
+
throw new Error(`No loader available for ${extension} files (${filename})`);
|
|
228
|
+
}
|
|
229
|
+
return loader(testModule, filename);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function findNearestPackageType(filename, projectRoot, packageTypeCache) {
|
|
233
|
+
let currentDir = path.dirname(filename);
|
|
234
|
+
const visited = [];
|
|
235
|
+
|
|
236
|
+
while (currentDir.startsWith(projectRoot)) {
|
|
237
|
+
if (packageTypeCache.has(currentDir)) {
|
|
238
|
+
const cached = packageTypeCache.get(currentDir);
|
|
239
|
+
for (const visitedDir of visited) {
|
|
240
|
+
packageTypeCache.set(visitedDir, cached);
|
|
241
|
+
}
|
|
242
|
+
return cached;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
visited.push(currentDir);
|
|
246
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
247
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
248
|
+
let packageType = 'commonjs';
|
|
249
|
+
try {
|
|
250
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
251
|
+
if (parsed.type === 'module') {
|
|
252
|
+
packageType = 'module';
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
packageType = 'commonjs';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const visitedDir of visited) {
|
|
259
|
+
packageTypeCache.set(visitedDir, packageType);
|
|
260
|
+
}
|
|
261
|
+
return packageType;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const parentDir = path.dirname(currentDir);
|
|
265
|
+
if (parentDir === currentDir) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
currentDir = parentDir;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
for (const visitedDir of visited) {
|
|
272
|
+
packageTypeCache.set(visitedDir, 'commonjs');
|
|
273
|
+
}
|
|
274
|
+
return 'commonjs';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function getCompilerContext(compilerState, projectRoot, tsconfigPath, options = {}) {
|
|
278
|
+
if (compilerState.context) {
|
|
279
|
+
return compilerState.context;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let ts;
|
|
283
|
+
try {
|
|
284
|
+
ts = require('typescript');
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (options.optional) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
throw new Error(
|
|
290
|
+
"TypeScript-powered module loading requires the 'typescript' package. Install with: npm i -D typescript"
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
compilerState.context = loadCompilerContext(ts, tsconfigPath);
|
|
295
|
+
return compilerState.context;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function loadCompilerContext(ts, tsconfigPath) {
|
|
299
|
+
const compilerOptions = buildCompilerOptions(ts, DEFAULT_TS_COMPILER_OPTIONS);
|
|
300
|
+
const context = {
|
|
301
|
+
ts,
|
|
302
|
+
compilerOptions,
|
|
303
|
+
baseUrl: null,
|
|
304
|
+
pathMappings: []
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
if (!tsconfigPath || !fs.existsSync(tsconfigPath)) {
|
|
308
|
+
return context;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
312
|
+
if (configFile.error) {
|
|
313
|
+
throw new Error(formatDiagnostics(ts, [configFile.error]));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const parsedConfig = ts.parseJsonConfigFileContent(
|
|
317
|
+
configFile.config,
|
|
318
|
+
ts.sys,
|
|
319
|
+
path.dirname(tsconfigPath)
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (parsedConfig.errors && parsedConfig.errors.length > 0) {
|
|
323
|
+
throw new Error(formatDiagnostics(ts, parsedConfig.errors));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
context.compilerOptions = buildCompilerOptions(ts, parsedConfig.options);
|
|
327
|
+
context.baseUrl = parsedConfig.options.baseUrl ? path.resolve(parsedConfig.options.baseUrl) : null;
|
|
328
|
+
context.pathMappings = buildPathMappings(parsedConfig.options.baseUrl, parsedConfig.options.paths || {});
|
|
329
|
+
return context;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function buildCompilerOptions(ts, baseOptions = {}) {
|
|
333
|
+
return {
|
|
334
|
+
...baseOptions,
|
|
335
|
+
target: normalizeCompilerOption(baseOptions.target, ts.ScriptTarget.ES2020),
|
|
336
|
+
module: ts.ModuleKind.CommonJS,
|
|
337
|
+
moduleResolution: normalizeCompilerOption(
|
|
338
|
+
baseOptions.moduleResolution,
|
|
339
|
+
ts.ModuleResolutionKind.Node10
|
|
340
|
+
),
|
|
341
|
+
sourceMap: false,
|
|
342
|
+
inlineSourceMap: false,
|
|
343
|
+
inlineSources: false,
|
|
344
|
+
esModuleInterop: baseOptions.esModuleInterop !== false,
|
|
345
|
+
allowJs: true
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function normalizeCompilerOption(value, fallback) {
|
|
350
|
+
return typeof value === 'number' ? value : fallback;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function buildPathMappings(baseUrl, paths) {
|
|
354
|
+
if (!baseUrl || !paths) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return Object.entries(paths).map(([pattern, targets]) => {
|
|
359
|
+
const starIndex = pattern.indexOf('*');
|
|
360
|
+
return {
|
|
361
|
+
pattern,
|
|
362
|
+
prefix: starIndex === -1 ? pattern : pattern.slice(0, starIndex),
|
|
363
|
+
suffix: starIndex === -1 ? '' : pattern.slice(starIndex + 1),
|
|
364
|
+
targets: Array.isArray(targets) ? targets : []
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function resolveConfiguredRequest({ request, parentFile, projectRoot, compilerContext }) {
|
|
370
|
+
if (!compilerContext || !parentFile || !shouldHandleProjectFile(parentFile, projectRoot)) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (request.startsWith('.') || path.isAbsolute(request) || isBuiltinRequest(request)) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const aliasedTarget = resolvePathAlias(request, compilerContext);
|
|
379
|
+
if (aliasedTarget) {
|
|
380
|
+
return aliasedTarget;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (!compilerContext.baseUrl) {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return resolveCandidatePath(path.resolve(compilerContext.baseUrl, request));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function resolvePathAlias(request, compilerContext) {
|
|
391
|
+
for (const mapping of compilerContext.pathMappings) {
|
|
392
|
+
const match = matchPathPattern(mapping, request);
|
|
393
|
+
if (match === null) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const target of mapping.targets) {
|
|
398
|
+
const targetPath = target.includes('*') ? target.replace('*', match) : target;
|
|
399
|
+
const resolved = resolveCandidatePath(path.resolve(compilerContext.baseUrl, targetPath));
|
|
400
|
+
if (resolved) {
|
|
401
|
+
return resolved;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function matchPathPattern(mapping, request) {
|
|
410
|
+
if (!mapping.pattern.includes('*')) {
|
|
411
|
+
return mapping.pattern === request ? '' : null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!request.startsWith(mapping.prefix) || !request.endsWith(mapping.suffix)) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return request.slice(mapping.prefix.length, request.length - mapping.suffix.length);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function resolveCandidatePath(basePath) {
|
|
422
|
+
if (fs.existsSync(basePath) && fs.statSync(basePath).isFile()) {
|
|
423
|
+
return basePath;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
427
|
+
const filePath = `${basePath}${extension}`;
|
|
428
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
429
|
+
return filePath;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (fs.existsSync(basePath) && fs.statSync(basePath).isDirectory()) {
|
|
434
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
435
|
+
const indexPath = path.join(basePath, `index${extension}`);
|
|
436
|
+
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
|
|
437
|
+
return indexPath;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function isBuiltinRequest(request) {
|
|
446
|
+
if (request.startsWith('node:')) {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
return Module.builtinModules.includes(request);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function transpileSource({ source, filename, compilerContext }) {
|
|
453
|
+
const { ts } = compilerContext;
|
|
454
|
+
const compilerOptions = {
|
|
455
|
+
...compilerContext.compilerOptions
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
const extension = path.extname(filename).toLowerCase();
|
|
459
|
+
if ((extension === '.tsx' || extension === '.jsx') && compilerOptions.jsx === undefined) {
|
|
460
|
+
compilerOptions.jsx = ts.JsxEmit.ReactJSX;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const compiled = ts.transpileModule(source, {
|
|
464
|
+
compilerOptions,
|
|
465
|
+
fileName: filename,
|
|
466
|
+
reportDiagnostics: true
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (compiled.diagnostics && compiled.diagnostics.length > 0) {
|
|
470
|
+
const blockingDiagnostics = compiled.diagnostics.filter(
|
|
471
|
+
(diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error
|
|
472
|
+
);
|
|
473
|
+
if (blockingDiagnostics.length > 0) {
|
|
474
|
+
throw new Error(formatDiagnostics(ts, blockingDiagnostics));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return compiled.outputText;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function formatDiagnostics(ts, diagnostics) {
|
|
482
|
+
return diagnostics
|
|
483
|
+
.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n'))
|
|
484
|
+
.join('\n');
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
module.exports = {
|
|
488
|
+
createModuleLoader
|
|
489
|
+
};
|