@eggjs/core 6.4.0 → 6.5.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/README.md +22 -22
- package/dist/commonjs/egg.d.ts +17 -18
- package/dist/commonjs/egg.js +11 -11
- package/dist/commonjs/lifecycle.d.ts +2 -2
- package/dist/commonjs/lifecycle.js +16 -13
- package/dist/commonjs/loader/context_loader.js +2 -2
- package/dist/commonjs/loader/egg_loader.d.ts +13 -13
- package/dist/commonjs/loader/egg_loader.js +77 -62
- package/dist/commonjs/loader/file_loader.d.ts +3 -3
- package/dist/commonjs/loader/file_loader.js +22 -24
- package/dist/commonjs/singleton.d.ts +2 -2
- package/dist/commonjs/singleton.js +6 -7
- package/dist/commonjs/utils/index.d.ts +2 -2
- package/dist/commonjs/utils/index.js +4 -4
- package/dist/commonjs/utils/sequencify.d.ts +1 -1
- package/dist/commonjs/utils/sequencify.js +18 -13
- package/dist/commonjs/utils/timing.js +14 -8
- package/dist/esm/egg.d.ts +17 -18
- package/dist/esm/egg.js +13 -13
- package/dist/esm/lifecycle.d.ts +2 -2
- package/dist/esm/lifecycle.js +16 -13
- package/dist/esm/loader/context_loader.js +2 -2
- package/dist/esm/loader/egg_loader.d.ts +13 -13
- package/dist/esm/loader/egg_loader.js +80 -65
- package/dist/esm/loader/file_loader.d.ts +3 -3
- package/dist/esm/loader/file_loader.js +23 -25
- package/dist/esm/singleton.d.ts +2 -2
- package/dist/esm/singleton.js +6 -7
- package/dist/esm/utils/index.d.ts +2 -2
- package/dist/esm/utils/index.js +4 -4
- package/dist/esm/utils/sequencify.d.ts +1 -1
- package/dist/esm/utils/sequencify.js +18 -13
- package/dist/esm/utils/timing.js +14 -8
- package/dist/package.json +1 -1
- package/package.json +16 -6
- package/src/egg.ts +161 -61
- package/src/lifecycle.ts +72 -33
- package/src/loader/context_loader.ts +9 -7
- package/src/loader/egg_loader.ts +445 -183
- package/src/loader/file_loader.ts +78 -37
- package/src/singleton.ts +64 -26
- package/src/utils/index.ts +20 -13
- package/src/utils/sequencify.ts +50 -15
- package/src/utils/timing.ts +27 -13
|
@@ -2,10 +2,17 @@ import assert from 'node:assert';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { debuglog } from 'node:util';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
|
|
5
6
|
import globby from 'globby';
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
isClass,
|
|
9
|
+
isGeneratorFunction,
|
|
10
|
+
isAsyncFunction,
|
|
11
|
+
isPrimitive,
|
|
12
|
+
} from 'is-type-of';
|
|
7
13
|
import { isSupportTypeScript } from '@eggjs/utils';
|
|
8
|
-
|
|
14
|
+
|
|
15
|
+
import utils, { type Fun } from '../utils/index.js';
|
|
9
16
|
|
|
10
17
|
const debug = debuglog('@eggjs/core/file_loader');
|
|
11
18
|
|
|
@@ -19,13 +26,17 @@ export enum CaseStyle {
|
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export type CaseStyleFunction = (filepath: string) => string[];
|
|
22
|
-
export type FileLoaderInitializer = (
|
|
29
|
+
export type FileLoaderInitializer = (
|
|
30
|
+
exports: unknown,
|
|
31
|
+
options: { path: string; pathName: string }
|
|
32
|
+
) => unknown;
|
|
23
33
|
export type FileLoaderFilter = (exports: unknown) => boolean;
|
|
24
34
|
|
|
25
35
|
export interface FileLoaderOptions {
|
|
26
36
|
/** directories to be loaded */
|
|
27
37
|
directory: string | string[];
|
|
28
38
|
/** attach the target object from loaded files */
|
|
39
|
+
|
|
29
40
|
target: Record<string, any>;
|
|
30
41
|
/** match the files when load, support glob, default to all js files */
|
|
31
42
|
match?: string | string[];
|
|
@@ -38,6 +49,7 @@ export interface FileLoaderOptions {
|
|
|
38
49
|
/** determine whether override the property when get the same name */
|
|
39
50
|
override?: boolean;
|
|
40
51
|
/** an object that be the argument when invoke the function */
|
|
52
|
+
|
|
41
53
|
inject?: Record<string, any>;
|
|
42
54
|
/** a function that filter the exports which can be loaded */
|
|
43
55
|
filter?: FileLoaderFilter;
|
|
@@ -65,7 +77,8 @@ export class FileLoader {
|
|
|
65
77
|
return EXPORTS;
|
|
66
78
|
}
|
|
67
79
|
|
|
68
|
-
readonly options: FileLoaderOptions &
|
|
80
|
+
readonly options: FileLoaderOptions &
|
|
81
|
+
Required<Pick<FileLoaderOptions, 'caseStyle'>>;
|
|
69
82
|
|
|
70
83
|
/**
|
|
71
84
|
* @class
|
|
@@ -101,7 +114,7 @@ export class FileLoader {
|
|
|
101
114
|
/**
|
|
102
115
|
* attach items to target object. Mapping the directory to properties.
|
|
103
116
|
* `app/controller/group/repository.js` => `target.group.repository`
|
|
104
|
-
* @
|
|
117
|
+
* @returns {Object} target
|
|
105
118
|
* @since 1.0.0
|
|
106
119
|
*/
|
|
107
120
|
async load(): Promise<object> {
|
|
@@ -111,12 +124,15 @@ export class FileLoader {
|
|
|
111
124
|
debug('loading item: %o', item);
|
|
112
125
|
// item { properties: [ 'a', 'b', 'c'], exports }
|
|
113
126
|
// => target.a.b.c = exports
|
|
127
|
+
// oxlint-disable-next-line unicorn/no-array-reduce
|
|
114
128
|
item.properties.reduce((target, property, index) => {
|
|
115
129
|
let obj;
|
|
116
130
|
const properties = item.properties.slice(0, index + 1).join('.');
|
|
117
131
|
if (index === item.properties.length - 1) {
|
|
118
|
-
if (property in target) {
|
|
119
|
-
|
|
132
|
+
if (property in target && !this.options.override) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`can't overwrite property '${properties}' from ${target[property][FULLPATH]} by ${item.fullpath}`
|
|
135
|
+
);
|
|
120
136
|
}
|
|
121
137
|
obj = item.exports;
|
|
122
138
|
if (obj && !isPrimitive(obj)) {
|
|
@@ -157,37 +173,43 @@ export class FileLoader {
|
|
|
157
173
|
* `Properties` is an array that contains the directory of a filepath.
|
|
158
174
|
*
|
|
159
175
|
* `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.
|
|
160
|
-
* @
|
|
176
|
+
* @returns {Array} items
|
|
161
177
|
* @since 1.0.0
|
|
162
178
|
*/
|
|
163
179
|
protected async parse(): Promise<FileLoaderParseItem[]> {
|
|
164
180
|
let files = this.options.match;
|
|
165
|
-
if (
|
|
166
|
-
files =
|
|
167
|
-
? [ '**/*.(js|ts)', '!**/*.d.ts' ]
|
|
168
|
-
: [ '**/*.js' ];
|
|
181
|
+
if (files) {
|
|
182
|
+
files = Array.isArray(files) ? files : [files];
|
|
169
183
|
} else {
|
|
170
|
-
files =
|
|
184
|
+
files = isSupportTypeScript()
|
|
185
|
+
? ['**/*.(js|ts)', '!**/*.d.ts']
|
|
186
|
+
: ['**/*.js'];
|
|
171
187
|
}
|
|
172
188
|
|
|
173
189
|
let ignore = this.options.ignore;
|
|
174
190
|
if (ignore) {
|
|
175
|
-
ignore = Array.isArray(ignore) ? ignore : [
|
|
191
|
+
ignore = Array.isArray(ignore) ? ignore : [ignore];
|
|
176
192
|
ignore = ignore.filter(f => !!f).map(f => '!' + f);
|
|
177
193
|
files = files.concat(ignore);
|
|
178
194
|
}
|
|
179
195
|
|
|
180
196
|
let directories = this.options.directory;
|
|
181
197
|
if (!Array.isArray(directories)) {
|
|
182
|
-
directories = [
|
|
198
|
+
directories = [directories];
|
|
183
199
|
}
|
|
184
200
|
|
|
185
|
-
const filter =
|
|
201
|
+
const filter =
|
|
202
|
+
typeof this.options.filter === 'function' ? this.options.filter : null;
|
|
186
203
|
const items: FileLoaderParseItem[] = [];
|
|
187
204
|
debug('[parse] parsing directories: %j', directories);
|
|
188
205
|
for (const directory of directories) {
|
|
189
206
|
const filepaths = globby.sync(files, { cwd: directory });
|
|
190
|
-
debug(
|
|
207
|
+
debug(
|
|
208
|
+
'[parse] globby files: %o, cwd: %o => %o',
|
|
209
|
+
files,
|
|
210
|
+
directory,
|
|
211
|
+
filepaths
|
|
212
|
+
);
|
|
191
213
|
for (const filepath of filepaths) {
|
|
192
214
|
const fullpath = path.join(directory, filepath);
|
|
193
215
|
if (!fs.statSync(fullpath).isFile()) continue;
|
|
@@ -202,12 +224,17 @@ export class FileLoader {
|
|
|
202
224
|
// app/service/foo/bar.js => [ 'foo', 'bar' ]
|
|
203
225
|
const properties = getProperties(filepath, this.options.caseStyle);
|
|
204
226
|
// app/service/foo/bar.js => service.foo.bar
|
|
205
|
-
const pathName =
|
|
227
|
+
const pathName =
|
|
228
|
+
directory.split(/[/\\]/).slice(-1) + '.' + properties.join('.');
|
|
206
229
|
// get exports from the file
|
|
207
230
|
const exports = await getExports(fullpath, this.options, pathName);
|
|
208
231
|
|
|
209
232
|
// ignore exports when it's null or false returned by filter function
|
|
210
|
-
if (
|
|
233
|
+
if (
|
|
234
|
+
exports === null ||
|
|
235
|
+
exports === undefined ||
|
|
236
|
+
(filter && filter(exports) === false)
|
|
237
|
+
) {
|
|
211
238
|
continue;
|
|
212
239
|
}
|
|
213
240
|
|
|
@@ -218,7 +245,12 @@ export class FileLoader {
|
|
|
218
245
|
}
|
|
219
246
|
|
|
220
247
|
items.push({ fullpath, properties, exports });
|
|
221
|
-
debug(
|
|
248
|
+
debug(
|
|
249
|
+
'[parse] parse %s, properties %j, exports %o',
|
|
250
|
+
fullpath,
|
|
251
|
+
properties,
|
|
252
|
+
exports
|
|
253
|
+
);
|
|
222
254
|
}
|
|
223
255
|
}
|
|
224
256
|
|
|
@@ -228,11 +260,17 @@ export class FileLoader {
|
|
|
228
260
|
|
|
229
261
|
// convert file path to an array of properties
|
|
230
262
|
// a/b/c.js => ['a', 'b', 'c']
|
|
231
|
-
function getProperties(
|
|
263
|
+
function getProperties(
|
|
264
|
+
filepath: string,
|
|
265
|
+
caseStyle: CaseStyle | CaseStyleFunction
|
|
266
|
+
) {
|
|
232
267
|
// if caseStyle is function, return the result of function
|
|
233
268
|
if (typeof caseStyle === 'function') {
|
|
234
269
|
const result = caseStyle(filepath);
|
|
235
|
-
assert(
|
|
270
|
+
assert(
|
|
271
|
+
Array.isArray(result),
|
|
272
|
+
`caseStyle expect an array, but got ${JSON.stringify(result)}`
|
|
273
|
+
);
|
|
236
274
|
return result;
|
|
237
275
|
}
|
|
238
276
|
// use default camelize
|
|
@@ -241,7 +279,11 @@ function getProperties(filepath: string, caseStyle: CaseStyle | CaseStyleFunctio
|
|
|
241
279
|
|
|
242
280
|
// Get exports from filepath
|
|
243
281
|
// If exports is null/undefined, it will be ignored
|
|
244
|
-
async function getExports(
|
|
282
|
+
async function getExports(
|
|
283
|
+
fullpath: string,
|
|
284
|
+
options: FileLoaderOptions,
|
|
285
|
+
pathName: string
|
|
286
|
+
) {
|
|
245
287
|
let exports = await utils.loadFile(fullpath);
|
|
246
288
|
// process exports as you like
|
|
247
289
|
if (options.initializer) {
|
|
@@ -250,7 +292,9 @@ async function getExports(fullpath: string, options: FileLoaderOptions, pathName
|
|
|
250
292
|
}
|
|
251
293
|
|
|
252
294
|
if (isGeneratorFunction(exports)) {
|
|
253
|
-
throw new TypeError(
|
|
295
|
+
throw new TypeError(
|
|
296
|
+
`Support for generators was removed, fullpath: ${fullpath}`
|
|
297
|
+
);
|
|
254
298
|
}
|
|
255
299
|
|
|
256
300
|
// return exports when it's a class or async function
|
|
@@ -269,7 +313,7 @@ async function getExports(fullpath: string, options: FileLoaderOptions, pathName
|
|
|
269
313
|
// }
|
|
270
314
|
if (options.call && typeof exports === 'function') {
|
|
271
315
|
exports = exports(options.inject);
|
|
272
|
-
if (exports
|
|
316
|
+
if (exports !== null && exports !== undefined) {
|
|
273
317
|
return exports;
|
|
274
318
|
}
|
|
275
319
|
}
|
|
@@ -279,7 +323,7 @@ async function getExports(fullpath: string, options: FileLoaderOptions, pathName
|
|
|
279
323
|
}
|
|
280
324
|
|
|
281
325
|
function defaultCamelize(filepath: string, caseStyle: CaseStyle) {
|
|
282
|
-
const properties = filepath.
|
|
326
|
+
const properties = filepath.slice(0, filepath.lastIndexOf('.')).split('/');
|
|
283
327
|
return properties.map(property => {
|
|
284
328
|
if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
|
|
285
329
|
throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);
|
|
@@ -291,18 +335,15 @@ function defaultCamelize(filepath: string, caseStyle: CaseStyle) {
|
|
|
291
335
|
// FooBar.js > FooBar
|
|
292
336
|
// FooBar.js > FooBar
|
|
293
337
|
// FooBar.js > fooBar (if lowercaseFirst is true)
|
|
294
|
-
property = property.
|
|
338
|
+
property = property.replaceAll(/[_-][a-z]/gi, s =>
|
|
339
|
+
s.slice(1).toUpperCase()
|
|
340
|
+
);
|
|
295
341
|
let first = property[0];
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
case 'upper':
|
|
301
|
-
first = first.toUpperCase();
|
|
302
|
-
break;
|
|
303
|
-
case 'camel':
|
|
304
|
-
default:
|
|
342
|
+
if (caseStyle === CaseStyle.lower) {
|
|
343
|
+
first = first.toLowerCase();
|
|
344
|
+
} else if (caseStyle === CaseStyle.upper) {
|
|
345
|
+
first = first.toUpperCase();
|
|
305
346
|
}
|
|
306
|
-
return first + property.
|
|
347
|
+
return first + property.slice(1);
|
|
307
348
|
});
|
|
308
349
|
}
|
package/src/singleton.ts
CHANGED
|
@@ -2,8 +2,11 @@ import assert from 'node:assert';
|
|
|
2
2
|
import { isAsyncFunction } from 'is-type-of';
|
|
3
3
|
import type { EggCore } from './egg.js';
|
|
4
4
|
|
|
5
|
-
export type SingletonCreateMethod =
|
|
6
|
-
|
|
5
|
+
export type SingletonCreateMethod = (
|
|
6
|
+
config: Record<string, any>,
|
|
7
|
+
app: EggCore,
|
|
8
|
+
clientName: string
|
|
9
|
+
) => unknown | Promise<unknown>;
|
|
7
10
|
|
|
8
11
|
export interface SingletonOptions {
|
|
9
12
|
name: string;
|
|
@@ -19,10 +22,22 @@ export class Singleton<T = any> {
|
|
|
19
22
|
readonly options: Record<string, any>;
|
|
20
23
|
|
|
21
24
|
constructor(options: SingletonOptions) {
|
|
22
|
-
assert(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
assert(
|
|
26
|
+
options.name,
|
|
27
|
+
'[@eggjs/core/singleton] Singleton#constructor options.name is required'
|
|
28
|
+
);
|
|
29
|
+
assert(
|
|
30
|
+
options.app,
|
|
31
|
+
'[@eggjs/core/singleton] Singleton#constructor options.app is required'
|
|
32
|
+
);
|
|
33
|
+
assert(
|
|
34
|
+
options.create,
|
|
35
|
+
'[@eggjs/core/singleton] Singleton#constructor options.create is required'
|
|
36
|
+
);
|
|
37
|
+
assert(
|
|
38
|
+
!(options.name in options.app),
|
|
39
|
+
`[@eggjs/core/singleton] ${options.name} is already exists in app`
|
|
40
|
+
);
|
|
26
41
|
this.app = options.app;
|
|
27
42
|
this.name = options.name;
|
|
28
43
|
this.create = options.create;
|
|
@@ -35,8 +50,10 @@ export class Singleton<T = any> {
|
|
|
35
50
|
|
|
36
51
|
initSync() {
|
|
37
52
|
const options = this.options;
|
|
38
|
-
assert(
|
|
39
|
-
|
|
53
|
+
assert(
|
|
54
|
+
!(options.client && options.clients),
|
|
55
|
+
`[@eggjs/core/singleton] ${this.name} can not set options.client and options.clients both`
|
|
56
|
+
);
|
|
40
57
|
|
|
41
58
|
// alias app[name] as client, but still support createInstance method
|
|
42
59
|
if (options.client) {
|
|
@@ -48,10 +65,10 @@ export class Singleton<T = any> {
|
|
|
48
65
|
|
|
49
66
|
// multi client, use app[name].getSingletonInstance(id)
|
|
50
67
|
if (options.clients) {
|
|
51
|
-
Object.keys(options.clients)
|
|
68
|
+
for (const id of Object.keys(options.clients)) {
|
|
52
69
|
const client = this.createInstance(options.clients[id], id);
|
|
53
70
|
this.clients.set(id, client);
|
|
54
|
-
}
|
|
71
|
+
}
|
|
55
72
|
this.#setClientToApp(this);
|
|
56
73
|
return;
|
|
57
74
|
}
|
|
@@ -62,12 +79,17 @@ export class Singleton<T = any> {
|
|
|
62
79
|
|
|
63
80
|
async initAsync() {
|
|
64
81
|
const options = this.options;
|
|
65
|
-
assert(
|
|
66
|
-
|
|
82
|
+
assert(
|
|
83
|
+
!(options.client && options.clients),
|
|
84
|
+
`[@eggjs/core/singleton] ${this.name} can not set options.client and options.clients both`
|
|
85
|
+
);
|
|
67
86
|
|
|
68
87
|
// alias app[name] as client, but still support createInstance method
|
|
69
88
|
if (options.client) {
|
|
70
|
-
const client = await this.createInstanceAsync(
|
|
89
|
+
const client = await this.createInstanceAsync(
|
|
90
|
+
options.client,
|
|
91
|
+
options.name
|
|
92
|
+
);
|
|
71
93
|
this.#setClientToApp(client);
|
|
72
94
|
this.#extendDynamicMethods(client);
|
|
73
95
|
return;
|
|
@@ -75,10 +97,13 @@ export class Singleton<T = any> {
|
|
|
75
97
|
|
|
76
98
|
// multi client, use app[name].getInstance(id)
|
|
77
99
|
if (options.clients) {
|
|
78
|
-
await Promise.all(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
await Promise.all(
|
|
101
|
+
Object.keys(options.clients).map((id: string) => {
|
|
102
|
+
return this.createInstanceAsync(options.clients[id], id).then(
|
|
103
|
+
client => this.clients.set(id, client)
|
|
104
|
+
);
|
|
105
|
+
})
|
|
106
|
+
);
|
|
82
107
|
this.#setClientToApp(this);
|
|
83
108
|
return;
|
|
84
109
|
}
|
|
@@ -95,26 +120,32 @@ export class Singleton<T = any> {
|
|
|
95
120
|
* @deprecated please use `getSingletonInstance(id)` instead
|
|
96
121
|
*/
|
|
97
122
|
get(id: string) {
|
|
98
|
-
return this.clients.get(id)
|
|
123
|
+
return this.clients.get(id) as T;
|
|
99
124
|
}
|
|
100
125
|
|
|
101
126
|
/**
|
|
102
127
|
* Get singleton instance by id
|
|
103
128
|
*/
|
|
104
129
|
getSingletonInstance(id: string) {
|
|
105
|
-
return this.clients.get(id)
|
|
130
|
+
return this.clients.get(id) as T;
|
|
106
131
|
}
|
|
107
132
|
|
|
108
133
|
createInstance(config: Record<string, any>, clientName: string) {
|
|
109
134
|
// async creator only support createInstanceAsync
|
|
110
|
-
assert(
|
|
111
|
-
|
|
135
|
+
assert(
|
|
136
|
+
!isAsyncFunction(this.create),
|
|
137
|
+
`[@eggjs/core/singleton] ${this.name} only support asynchronous creation, please use createInstanceAsync`
|
|
138
|
+
);
|
|
112
139
|
// options.default will be merge in to options.clients[id]
|
|
113
140
|
config = {
|
|
114
141
|
...this.options.default,
|
|
115
142
|
...config,
|
|
116
143
|
};
|
|
117
|
-
return (this.create as SingletonCreateMethod)(
|
|
144
|
+
return (this.create as SingletonCreateMethod)(
|
|
145
|
+
config,
|
|
146
|
+
this.app,
|
|
147
|
+
clientName
|
|
148
|
+
) as T;
|
|
118
149
|
}
|
|
119
150
|
|
|
120
151
|
async createInstanceAsync(config: Record<string, any>, clientName: string) {
|
|
@@ -123,12 +154,18 @@ export class Singleton<T = any> {
|
|
|
123
154
|
...this.options.default,
|
|
124
155
|
...config,
|
|
125
156
|
};
|
|
126
|
-
return await this.create(config, this.app, clientName) as T;
|
|
157
|
+
return (await this.create(config, this.app, clientName)) as T;
|
|
127
158
|
}
|
|
128
159
|
|
|
129
160
|
#extendDynamicMethods(client: any) {
|
|
130
|
-
assert(
|
|
131
|
-
|
|
161
|
+
assert(
|
|
162
|
+
!client.createInstance,
|
|
163
|
+
'[@eggjs/core/singleton] singleton instance should not have createInstance method'
|
|
164
|
+
);
|
|
165
|
+
assert(
|
|
166
|
+
!client.createInstanceAsync,
|
|
167
|
+
'[@eggjs/core/singleton] singleton instance should not have createInstanceAsync method'
|
|
168
|
+
);
|
|
132
169
|
|
|
133
170
|
try {
|
|
134
171
|
let extendable = client;
|
|
@@ -142,7 +179,8 @@ export class Singleton<T = any> {
|
|
|
142
179
|
} catch (err) {
|
|
143
180
|
this.app.coreLogger.warn(
|
|
144
181
|
'[@eggjs/core/singleton] %s dynamic create is disabled because of client is un-extendable',
|
|
145
|
-
this.name
|
|
182
|
+
this.name
|
|
183
|
+
);
|
|
146
184
|
this.app.coreLogger.warn(err);
|
|
147
185
|
}
|
|
148
186
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -3,20 +3,22 @@ import path from 'node:path';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { stat } from 'node:fs/promises';
|
|
5
5
|
import BuiltinModule from 'node:module';
|
|
6
|
+
|
|
6
7
|
import { importResolve, importModule } from '@eggjs/utils';
|
|
7
8
|
|
|
8
9
|
const debug = debuglog('@eggjs/core/utils');
|
|
9
10
|
|
|
10
|
-
export type Fun = (...args:
|
|
11
|
+
export type Fun = (...args: unknown[]) => unknown;
|
|
11
12
|
|
|
12
13
|
// Guard against poorly mocked module constructors.
|
|
13
|
-
const Module =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
const Module =
|
|
15
|
+
typeof module !== 'undefined' && module.constructor.length > 1
|
|
16
|
+
? module.constructor
|
|
17
|
+
: /* istanbul ignore next */
|
|
18
|
+
BuiltinModule;
|
|
17
19
|
|
|
18
20
|
const extensions = (Module as any)._extensions;
|
|
19
|
-
const extensionNames = Object.keys(extensions).concat([
|
|
21
|
+
const extensionNames = Object.keys(extensions).concat(['.cjs', '.mjs']);
|
|
20
22
|
debug('Module extensions: %j', extensionNames);
|
|
21
23
|
|
|
22
24
|
function getCalleeFromStack(withLine?: boolean, stackIndex?: number) {
|
|
@@ -28,6 +30,7 @@ function getCalleeFromStack(withLine?: boolean, stackIndex?: number) {
|
|
|
28
30
|
Error.stackTraceLimit = 5;
|
|
29
31
|
|
|
30
32
|
// capture the stack
|
|
33
|
+
|
|
31
34
|
const obj: any = {};
|
|
32
35
|
Error.captureStackTrace(obj);
|
|
33
36
|
let callSite = obj.stack[stackIndex];
|
|
@@ -58,7 +61,9 @@ export default {
|
|
|
58
61
|
console.trace('[@eggjs/core/deprecated] %s', message);
|
|
59
62
|
} else {
|
|
60
63
|
console.log('[@eggjs/core/deprecated] %s', message);
|
|
61
|
-
console.log(
|
|
64
|
+
console.log(
|
|
65
|
+
'[@eggjs/core/deprecated] set NODE_DEBUG=@eggjs/core/utils can show call stack'
|
|
66
|
+
);
|
|
62
67
|
}
|
|
63
68
|
},
|
|
64
69
|
|
|
@@ -83,13 +88,15 @@ export default {
|
|
|
83
88
|
}
|
|
84
89
|
const obj = await importModule(filepath, { importDefaultOnly: true });
|
|
85
90
|
return obj;
|
|
86
|
-
} catch (e
|
|
87
|
-
if (!e
|
|
91
|
+
} catch (e) {
|
|
92
|
+
if (!(e instanceof Error)) {
|
|
88
93
|
// ts error: test/fixtures/apps/app-ts/app/extend/context.ts(5,17): error TS2339: Property 'url' does not exist on type 'Context'
|
|
89
94
|
console.trace(e);
|
|
90
95
|
throw e;
|
|
91
96
|
}
|
|
92
|
-
const err = new Error(
|
|
97
|
+
const err = new Error(
|
|
98
|
+
`[@eggjs/core] load file: ${filepath}, error: ${e.message}`
|
|
99
|
+
);
|
|
93
100
|
err.cause = e;
|
|
94
101
|
debug('[loadFile] handle %s error: %s', filepath, e);
|
|
95
102
|
throw err;
|
|
@@ -100,9 +107,9 @@ export default {
|
|
|
100
107
|
return importResolve(filepath, options);
|
|
101
108
|
},
|
|
102
109
|
|
|
103
|
-
methods: [
|
|
110
|
+
methods: ['head', 'options', 'get', 'put', 'patch', 'post', 'delete'],
|
|
104
111
|
|
|
105
|
-
async callFn(fn: Fun, args?:
|
|
112
|
+
async callFn(fn: Fun, args?: unknown[], ctx?: unknown) {
|
|
106
113
|
args = args || [];
|
|
107
114
|
if (typeof fn !== 'function') return;
|
|
108
115
|
return ctx ? fn.call(ctx, ...args) : fn(...args);
|
|
@@ -120,6 +127,6 @@ export default {
|
|
|
120
127
|
* Capture call site stack from v8.
|
|
121
128
|
* https://github.com/v8/v8/wiki/Stack-Trace-API
|
|
122
129
|
*/
|
|
123
|
-
function prepareObjectStackTrace(_obj:
|
|
130
|
+
function prepareObjectStackTrace(_obj: unknown, stack: unknown) {
|
|
124
131
|
return stack;
|
|
125
132
|
}
|
package/src/utils/sequencify.ts
CHANGED
|
@@ -12,27 +12,59 @@ export interface SequencifyTask {
|
|
|
12
12
|
optionalDependencies: string[];
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function sequence(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
names
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
function sequence(
|
|
16
|
+
// oxlint-disable-next-line max-params
|
|
17
|
+
tasks: Record<string, SequencifyTask>,
|
|
18
|
+
names: string[],
|
|
19
|
+
result: SequencifyResult,
|
|
20
|
+
missing: string[],
|
|
21
|
+
recursive: string[],
|
|
22
|
+
nest: string[],
|
|
23
|
+
optional: boolean,
|
|
24
|
+
parent: string
|
|
25
|
+
) {
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
if (result.requires[name]) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
21
30
|
const node = tasks[name];
|
|
22
31
|
if (!node) {
|
|
23
|
-
if (optional === true)
|
|
32
|
+
if (optional === true) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
24
35
|
missing.push(name);
|
|
25
36
|
} else if (nest.includes(name)) {
|
|
26
37
|
nest.push(name);
|
|
27
38
|
recursive.push(...nest.slice(0));
|
|
28
39
|
nest.pop();
|
|
29
|
-
} else if (
|
|
40
|
+
} else if (
|
|
41
|
+
node.dependencies.length > 0 ||
|
|
42
|
+
node.optionalDependencies.length > 0
|
|
43
|
+
) {
|
|
30
44
|
nest.push(name);
|
|
31
|
-
if (node.dependencies.length) {
|
|
32
|
-
sequence(
|
|
45
|
+
if (node.dependencies.length > 0) {
|
|
46
|
+
sequence(
|
|
47
|
+
tasks,
|
|
48
|
+
node.dependencies,
|
|
49
|
+
result,
|
|
50
|
+
missing,
|
|
51
|
+
recursive,
|
|
52
|
+
nest,
|
|
53
|
+
optional,
|
|
54
|
+
name
|
|
55
|
+
);
|
|
33
56
|
}
|
|
34
|
-
if (node.optionalDependencies.length) {
|
|
35
|
-
sequence(
|
|
57
|
+
if (node.optionalDependencies.length > 0) {
|
|
58
|
+
sequence(
|
|
59
|
+
tasks,
|
|
60
|
+
node.optionalDependencies,
|
|
61
|
+
result,
|
|
62
|
+
missing,
|
|
63
|
+
recursive,
|
|
64
|
+
nest,
|
|
65
|
+
true,
|
|
66
|
+
name
|
|
67
|
+
);
|
|
36
68
|
}
|
|
37
69
|
nest.pop();
|
|
38
70
|
}
|
|
@@ -43,12 +75,15 @@ function sequence(tasks: Record<string, SequencifyTask>, names: string[], result
|
|
|
43
75
|
if (!result.sequence.includes(name)) {
|
|
44
76
|
result.sequence.push(name);
|
|
45
77
|
}
|
|
46
|
-
}
|
|
78
|
+
}
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
// tasks: object with keys as task names
|
|
50
82
|
// names: array of task names
|
|
51
|
-
export
|
|
83
|
+
export function sequencify(
|
|
84
|
+
tasks: Record<string, SequencifyTask>,
|
|
85
|
+
names: string[]
|
|
86
|
+
) {
|
|
52
87
|
const result: SequencifyResult = {
|
|
53
88
|
sequence: [],
|
|
54
89
|
requires: {},
|
|
@@ -58,7 +93,7 @@ export default function sequencify(tasks: Record<string, SequencifyTask>, names:
|
|
|
58
93
|
|
|
59
94
|
sequence(tasks, names, result, missing, recursive, [], false, 'app');
|
|
60
95
|
|
|
61
|
-
if (missing.length || recursive.length) {
|
|
96
|
+
if (missing.length > 0 || recursive.length > 0) {
|
|
62
97
|
result.sequence = []; // results are incomplete at best, completely wrong at worst, remove them to avoid confusion
|
|
63
98
|
}
|
|
64
99
|
|