@midwayjs/mock 4.0.3 → 4.1.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/dist/creator.js CHANGED
@@ -15,6 +15,7 @@ const fs_1 = require("fs");
15
15
  const yaml = require("js-yaml");
16
16
  const getRawBody = require("raw-body");
17
17
  const functional_1 = require("@midwayjs/core/functional");
18
+ const sourceLoader_1 = require("./sourceLoader");
18
19
  const debug = (0, util_1.debuglog)('midway:debug');
19
20
  process.setMaxListeners(0);
20
21
  function formatPath(baseDir, p) {
@@ -128,18 +129,24 @@ async function create(appDir, options = {}) {
128
129
  });
129
130
  options.moduleLoadType = pkgJSON?.type === 'module' ? 'esm' : 'commonjs';
130
131
  }
132
+ if (!options.moduleLoader &&
133
+ options.moduleLoadType === 'esm' &&
134
+ (0, core_1.isTypeScriptEnvironment)()) {
135
+ options.moduleLoader = (0, sourceLoader_1.createSourceModuleLoader)();
136
+ }
137
+ const moduleLoader = options.moduleLoader ?? core_1.loadModule;
131
138
  if (options.baseDir) {
132
139
  if (!(0, path_1.isAbsolute)(options.baseDir)) {
133
140
  options.baseDir = (0, path_1.join)(appDir, options.baseDir);
134
141
  }
135
- await (0, core_1.loadModule)((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
142
+ await moduleLoader((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
136
143
  safeLoad: true,
137
144
  loadMode: options.moduleLoadType,
138
145
  });
139
146
  }
140
147
  else if (appDir) {
141
148
  options.baseDir = (0, path_1.join)(appDir, 'src');
142
- await (0, core_1.loadModule)((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
149
+ await moduleLoader((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
143
150
  safeLoad: true,
144
151
  loadMode: options.moduleLoadType,
145
152
  });
@@ -292,15 +299,21 @@ async function createFunctionApp(baseDir, options = {}, customFrameworkModule) {
292
299
  enableCache: false,
293
300
  });
294
301
  options.moduleLoadType = pkgJSON?.type === 'module' ? 'esm' : 'commonjs';
302
+ if (!options.moduleLoader &&
303
+ options.moduleLoadType === 'esm' &&
304
+ (0, core_1.isTypeScriptEnvironment)()) {
305
+ options.moduleLoader = (0, sourceLoader_1.createSourceModuleLoader)();
306
+ }
307
+ const moduleLoader = options.moduleLoader ?? core_1.loadModule;
295
308
  if (options.baseDir) {
296
- await (0, core_1.loadModule)((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
309
+ await moduleLoader((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
297
310
  safeLoad: true,
298
311
  loadMode: options.moduleLoadType,
299
312
  });
300
313
  }
301
314
  else if (options.appDir) {
302
315
  options.baseDir = `${options.appDir}/src`;
303
- await (0, core_1.loadModule)((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
316
+ await moduleLoader((0, path_1.join)(`${options.baseDir}`, getFileNameWithSuffix('interface')), {
304
317
  safeLoad: true,
305
318
  loadMode: options.moduleLoadType,
306
319
  });
@@ -8,6 +8,14 @@ export interface MockBootstrapOptions extends IMidwayBootstrapOptions, ILifeCycl
8
8
  entryFile?: string;
9
9
  bootstrapMode?: 'faas' | 'app';
10
10
  initializeMethodName?: string;
11
+ moduleLoader?: (p: string, options?: {
12
+ enableCache?: boolean;
13
+ loadMode?: 'commonjs' | 'esm';
14
+ safeLoad?: boolean;
15
+ warnOnLoadError?: boolean;
16
+ extraModuleRoot?: string[];
17
+ importQuery?: string;
18
+ }) => Promise<any>;
11
19
  }
12
20
  export type ComponentModule = {
13
21
  Configuration: new () => any;
@@ -0,0 +1,3 @@
1
+ import { ModuleLoader } from '@midwayjs/core';
2
+ export declare function createSourceModuleLoader(baseLoader?: ModuleLoader): ModuleLoader;
3
+ //# sourceMappingURL=sourceLoader.d.ts.map
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSourceModuleLoader = createSourceModuleLoader;
4
+ const core_1 = require("@midwayjs/core");
5
+ const path_1 = require("path");
6
+ const fs_1 = require("fs");
7
+ const util_1 = require("util");
8
+ const url_1 = require("url");
9
+ const crypto = require("crypto");
10
+ const debug = (0, util_1.debuglog)('midway:debug');
11
+ let cachedTypeScriptCompiler;
12
+ function resolveRelativeEsmSpecifierPath(importerFile, specifier) {
13
+ if (!specifier ||
14
+ (!specifier.startsWith('./') && !specifier.startsWith('../'))) {
15
+ return undefined;
16
+ }
17
+ const absolute = (0, path_1.resolve)((0, path_1.dirname)(importerFile), specifier);
18
+ const candidates = [absolute];
19
+ if (/\.(mjs|cjs|js)$/i.test(specifier)) {
20
+ candidates.push(absolute.replace(/\.(mjs|cjs|js)$/i, '.mts'), absolute.replace(/\.(mjs|cjs|js)$/i, '.cts'), absolute.replace(/\.(mjs|cjs|js)$/i, '.ts'), absolute.replace(/\.(mjs|cjs|js)$/i, '.tsx'));
21
+ }
22
+ else if (!/\.[a-z0-9]+$/i.test(specifier)) {
23
+ candidates.push(`${absolute}.mts`, `${absolute}.cts`, `${absolute}.ts`, `${absolute}.tsx`, `${absolute}.mjs`, `${absolute}.cjs`, `${absolute}.js`, `${absolute}.json`, (0, path_1.join)(absolute, 'index.mts'), (0, path_1.join)(absolute, 'index.cts'), (0, path_1.join)(absolute, 'index.ts'), (0, path_1.join)(absolute, 'index.tsx'), (0, path_1.join)(absolute, 'index.mjs'), (0, path_1.join)(absolute, 'index.cjs'), (0, path_1.join)(absolute, 'index.js'), (0, path_1.join)(absolute, 'index.json'));
24
+ }
25
+ for (const item of candidates) {
26
+ if ((0, fs_1.existsSync)(item)) {
27
+ return item;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+ function shouldUseEsmSourceFallback(originErr, filePath, rewritten, source) {
33
+ if (rewritten !== source) {
34
+ return true;
35
+ }
36
+ if (!/\.(mts|cts|ts|tsx)$/i.test(filePath)) {
37
+ return false;
38
+ }
39
+ return (originErr?.code === 'ERR_UNKNOWN_FILE_EXTENSION' ||
40
+ originErr instanceof SyntaxError ||
41
+ originErr?.name === 'SyntaxError');
42
+ }
43
+ function formatFallbackImportSpecifier(fromFile, toFile) {
44
+ let specifier = (0, path_1.relative)((0, path_1.dirname)(fromFile), toFile).split(path_1.sep).join('/');
45
+ if (!specifier.startsWith('.')) {
46
+ specifier = `./${specifier}`;
47
+ }
48
+ return specifier;
49
+ }
50
+ function loadTypeScriptCompiler(sourceFile) {
51
+ if (cachedTypeScriptCompiler) {
52
+ return cachedTypeScriptCompiler;
53
+ }
54
+ const searchPaths = [(0, path_1.dirname)(sourceFile), process.cwd(), __dirname];
55
+ for (const item of searchPaths) {
56
+ try {
57
+ cachedTypeScriptCompiler = require(require.resolve('typescript', {
58
+ paths: [item],
59
+ }));
60
+ return cachedTypeScriptCompiler;
61
+ }
62
+ catch {
63
+ // try next path
64
+ }
65
+ }
66
+ }
67
+ function createCompiledEsmFallbackGraph(entryFile) {
68
+ const tempDir = (0, fs_1.mkdtempSync)((0, path_1.join)((0, path_1.dirname)(entryFile), '.midway-esm-fallback-'));
69
+ const compiledFileMap = new Map();
70
+ const tsCompiler = loadTypeScriptCompiler(entryFile);
71
+ const compileFile = (sourceFile) => {
72
+ const existed = compiledFileMap.get(sourceFile);
73
+ if (existed) {
74
+ return existed;
75
+ }
76
+ const compiledFile = (0, path_1.join)(tempDir, `${crypto.createHash('sha1').update(sourceFile).digest('hex')}.mjs`);
77
+ compiledFileMap.set(sourceFile, compiledFile);
78
+ if (sourceFile.endsWith('.json')) {
79
+ const jsonSource = (0, fs_1.readFileSync)(sourceFile, { encoding: 'utf-8' });
80
+ (0, fs_1.writeFileSync)(compiledFile, `export default ${jsonSource};`, {
81
+ encoding: 'utf-8',
82
+ });
83
+ return compiledFile;
84
+ }
85
+ const source = (0, fs_1.readFileSync)(sourceFile, { encoding: 'utf-8' });
86
+ const rewriteByPattern = (pattern, input) => {
87
+ return input.replace(pattern, (full, head, spec, tail) => {
88
+ const resolved = resolveRelativeEsmSpecifierPath(sourceFile, spec);
89
+ if (!resolved) {
90
+ return full;
91
+ }
92
+ const compiledDependency = compileFile(resolved);
93
+ const fallbackSpecifier = formatFallbackImportSpecifier(compiledFile, compiledDependency);
94
+ return `${head}${fallbackSpecifier}${tail}`;
95
+ });
96
+ };
97
+ let rewritten = source;
98
+ rewritten = rewriteByPattern(/(from\s+['"])([^'"]+)(['"])/g, rewritten);
99
+ rewritten = rewriteByPattern(/(import\s*\(\s*['"])([^'"]+)(['"]\s*\))/g, rewritten);
100
+ let output = rewritten;
101
+ if (/\.(mts|cts|ts|tsx)$/i.test(sourceFile)) {
102
+ if (!tsCompiler) {
103
+ throw new Error(`[mock]: can not transpile esm typescript file "${sourceFile}", please install "typescript" in current project`);
104
+ }
105
+ output = tsCompiler.transpileModule(rewritten, {
106
+ fileName: sourceFile,
107
+ compilerOptions: {
108
+ module: tsCompiler.ModuleKind.ESNext,
109
+ target: tsCompiler.ScriptTarget.ES2020,
110
+ moduleResolution: tsCompiler.ModuleResolutionKind.NodeNext,
111
+ esModuleInterop: true,
112
+ allowSyntheticDefaultImports: true,
113
+ resolveJsonModule: true,
114
+ experimentalDecorators: true,
115
+ emitDecoratorMetadata: true,
116
+ useDefineForClassFields: false,
117
+ jsx: tsCompiler.JsxEmit.ReactJSX,
118
+ },
119
+ }).outputText;
120
+ }
121
+ (0, fs_1.writeFileSync)(compiledFile, output, { encoding: 'utf-8' });
122
+ return compiledFile;
123
+ };
124
+ return {
125
+ entryFile: compileFile(entryFile),
126
+ cleanup() {
127
+ (0, fs_1.rmSync)(tempDir, {
128
+ recursive: true,
129
+ force: true,
130
+ });
131
+ },
132
+ };
133
+ }
134
+ function rewriteRelativeEsmSource(importerFile, source) {
135
+ let changed = false;
136
+ const rewriteByPattern = (pattern, input) => {
137
+ return input.replace(pattern, (full, head, spec, tail) => {
138
+ const resolved = resolveRelativeEsmSpecifierPath(importerFile, spec);
139
+ if (!resolved) {
140
+ return full;
141
+ }
142
+ let fallback = (0, path_1.relative)((0, path_1.dirname)(importerFile), resolved)
143
+ .split(path_1.sep)
144
+ .join('/');
145
+ if (!fallback.startsWith('.')) {
146
+ fallback = `./${fallback}`;
147
+ }
148
+ if (fallback === spec) {
149
+ return full;
150
+ }
151
+ changed = true;
152
+ return `${head}${fallback}${tail}`;
153
+ });
154
+ };
155
+ let output = source;
156
+ output = rewriteByPattern(/(from\s+['"])([^'"]+)(['"])/g, output);
157
+ output = rewriteByPattern(/(import\s*\(\s*['"])([^'"]+)(['"]\s*\))/g, output);
158
+ return changed ? output : source;
159
+ }
160
+ async function importWithSpecifierFallback(p, importQuery) {
161
+ const fileUrl = (0, url_1.pathToFileURL)(p);
162
+ if (importQuery) {
163
+ fileUrl.searchParams.set('mwImportQuery', importQuery);
164
+ }
165
+ try {
166
+ return await import(fileUrl.href);
167
+ }
168
+ catch (originErr) {
169
+ const source = (0, fs_1.readFileSync)(p, { encoding: 'utf-8' });
170
+ const rewritten = rewriteRelativeEsmSource(p, source);
171
+ if (!shouldUseEsmSourceFallback(originErr, p, rewritten, source)) {
172
+ throw originErr;
173
+ }
174
+ const fallbackGraph = createCompiledEsmFallbackGraph(p);
175
+ try {
176
+ const fallbackUrl = (0, url_1.pathToFileURL)(fallbackGraph.entryFile);
177
+ if (importQuery) {
178
+ fallbackUrl.searchParams.set('mwImportQuery', importQuery);
179
+ }
180
+ return await import(fallbackUrl.href);
181
+ }
182
+ finally {
183
+ fallbackGraph.cleanup();
184
+ }
185
+ }
186
+ }
187
+ function createSourceModuleLoader(baseLoader = core_1.loadModule) {
188
+ return async (p, options = {}) => {
189
+ options.enableCache = options.enableCache ?? true;
190
+ options.safeLoad = options.safeLoad ?? false;
191
+ options.loadMode = options.loadMode ?? 'commonjs';
192
+ if (p.startsWith(`.${path_1.sep}`) || p.startsWith(`..${path_1.sep}`)) {
193
+ p = (0, path_1.resolve)((0, path_1.dirname)(module.parent.filename), p);
194
+ }
195
+ debug(`[mock]: source load module ${p}, cache: ${options.enableCache}, mode: ${options.loadMode}, safeLoad: ${options.safeLoad}`);
196
+ try {
197
+ if (options.enableCache &&
198
+ options.loadMode === 'esm' &&
199
+ !p.endsWith('.json')) {
200
+ try {
201
+ return await baseLoader(p, {
202
+ ...options,
203
+ safeLoad: false,
204
+ });
205
+ }
206
+ catch {
207
+ // Mock dev mode loads TS source files directly. This fallback stays in
208
+ // mock on purpose so core.loadModule() can remain a plain loader.
209
+ return await importWithSpecifierFallback(p, options.importQuery);
210
+ }
211
+ }
212
+ return await baseLoader(p, {
213
+ ...options,
214
+ safeLoad: false,
215
+ });
216
+ }
217
+ catch (err) {
218
+ if (!options.safeLoad) {
219
+ throw err;
220
+ }
221
+ if (options.warnOnLoadError &&
222
+ err.code !== 'MODULE_NOT_FOUND' &&
223
+ err.code !== 'ERR_MODULE_NOT_FOUND' &&
224
+ err.code !== 'ENOENT') {
225
+ console.warn(err);
226
+ }
227
+ debug(`[mock]: SafeLoadModule Warning\n\n${err.message}\n`);
228
+ return undefined;
229
+ }
230
+ };
231
+ }
232
+ //# sourceMappingURL=sourceLoader.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midwayjs/mock",
3
- "version": "4.0.3",
3
+ "version": "4.1.0",
4
4
  "description": "create your test app from midway framework",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "license": "MIT",
52
52
  "devDependencies": {
53
- "@midwayjs/core": "^4.0.3",
53
+ "@midwayjs/core": "^4.1.0",
54
54
  "@midwayjs/logger": "^4.0.0",
55
55
  "@types/amqplib": "0.10.8",
56
56
  "amqplib": "0.10.9",
@@ -64,12 +64,12 @@
64
64
  "@types/supertest": "2.0.16",
65
65
  "js-yaml": "4.1.1",
66
66
  "raw-body": "2.5.2",
67
- "supertest": "6.3.3"
67
+ "supertest": "6.3.4"
68
68
  },
69
69
  "author": "Harry Chen <czy88840616@gmail.com>",
70
70
  "repository": {
71
71
  "type": "git",
72
72
  "url": "https://github.com/midwayjs/midway.git"
73
73
  },
74
- "gitHead": "1eb7584f63456836b016f156a19a0f77a5256666"
74
+ "gitHead": "0e6259f761c64b844c4dcab49372c64902fbd7d8"
75
75
  }