te.js 1.3.0 → 2.0.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/.cursor/plans/ai_native_framework_features_5bb1a20a.plan.md +234 -0
- package/.cursor/plans/auto_error_fix_agent_e68979c5.plan.md +356 -0
- package/.cursor/plans/tejas_framework_test_suite_5e3c6fad.plan.md +168 -0
- package/.prettierignore +31 -0
- package/README.md +156 -14
- package/auto-docs/analysis/handler-analyzer.js +58 -0
- package/auto-docs/analysis/source-resolver.js +101 -0
- package/auto-docs/constants.js +37 -0
- package/auto-docs/index.js +146 -0
- package/auto-docs/llm/index.js +6 -0
- package/auto-docs/llm/parse.js +88 -0
- package/auto-docs/llm/prompts.js +222 -0
- package/auto-docs/llm/provider.js +187 -0
- package/auto-docs/openapi/endpoint-processor.js +277 -0
- package/auto-docs/openapi/generator.js +107 -0
- package/auto-docs/openapi/level3.js +131 -0
- package/auto-docs/openapi/spec-builders.js +244 -0
- package/auto-docs/ui/docs-ui.js +186 -0
- package/auto-docs/utils/logger.js +17 -0
- package/auto-docs/utils/strip-usage.js +10 -0
- package/cli/docs-command.js +315 -0
- package/cli/fly-command.js +71 -0
- package/cli/index.js +57 -0
- package/database/index.js +163 -5
- package/database/mongodb.js +146 -0
- package/database/redis.js +201 -0
- package/docs/README.md +36 -0
- package/docs/ammo.md +362 -0
- package/docs/api-reference.md +489 -0
- package/docs/auto-docs.md +215 -0
- package/docs/cli.md +152 -0
- package/docs/configuration.md +233 -0
- package/docs/database.md +391 -0
- package/docs/error-handling.md +417 -0
- package/docs/file-uploads.md +334 -0
- package/docs/getting-started.md +181 -0
- package/docs/middleware.md +356 -0
- package/docs/rate-limiting.md +394 -0
- package/docs/routing.md +302 -0
- package/example/API_OVERVIEW.md +77 -0
- package/example/README.md +155 -0
- package/example/index.js +27 -2
- package/example/openapi.json +390 -0
- package/example/package.json +5 -2
- package/example/services/cache.service.js +25 -0
- package/example/services/user.service.js +42 -0
- package/example/start-redis.js +2 -0
- package/example/targets/cache.target.js +35 -0
- package/example/targets/index.target.js +11 -2
- package/example/targets/users.target.js +60 -0
- package/example/tejas.config.json +13 -1
- package/package.json +20 -5
- package/rate-limit/algorithms/fixed-window.js +141 -0
- package/rate-limit/algorithms/sliding-window.js +147 -0
- package/rate-limit/algorithms/token-bucket.js +115 -0
- package/rate-limit/base.js +165 -0
- package/rate-limit/index.js +147 -0
- package/rate-limit/storage/base.js +104 -0
- package/rate-limit/storage/memory.js +102 -0
- package/rate-limit/storage/redis.js +88 -0
- package/server/ammo/body-parser.js +152 -25
- package/server/ammo/enhancer.js +6 -2
- package/server/ammo.js +356 -327
- package/server/endpoint.js +21 -0
- package/server/handler.js +113 -87
- package/server/target.js +50 -9
- package/server/targets/registry.js +111 -6
- package/te.js +363 -137
- package/tests/auto-docs/handler-analyzer.test.js +44 -0
- package/tests/auto-docs/openapi-generator.test.js +103 -0
- package/tests/auto-docs/parse.test.js +63 -0
- package/tests/auto-docs/source-resolver.test.js +58 -0
- package/tests/helpers/index.js +37 -0
- package/tests/helpers/mock-http.js +342 -0
- package/tests/helpers/test-utils.js +446 -0
- package/tests/setup.test.js +148 -0
- package/utils/configuration.js +13 -10
- package/vitest.config.js +54 -0
- package/database/mongo.js +0 -67
- package/example/targets/user/user.target.js +0 -17
package/server/endpoint.js
CHANGED
|
@@ -7,6 +7,9 @@ class Endpoint {
|
|
|
7
7
|
this.path = '';
|
|
8
8
|
this.middlewares = [];
|
|
9
9
|
this.handler = null;
|
|
10
|
+
this.metadata = null;
|
|
11
|
+
/** Source group (e.g. target file id) for grouping in docs. Set by loader before register(). */
|
|
12
|
+
this.group = null;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
setPath(base, path) {
|
|
@@ -37,6 +40,16 @@ class Endpoint {
|
|
|
37
40
|
return this;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
setMetadata(metadata) {
|
|
44
|
+
this.metadata = metadata ?? null;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setGroup(group) {
|
|
49
|
+
this.group = group ?? null;
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
getPath() {
|
|
41
54
|
return this.path;
|
|
42
55
|
}
|
|
@@ -48,6 +61,14 @@ class Endpoint {
|
|
|
48
61
|
getHandler() {
|
|
49
62
|
return this.handler;
|
|
50
63
|
}
|
|
64
|
+
|
|
65
|
+
getMetadata() {
|
|
66
|
+
return this.metadata;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getGroup() {
|
|
70
|
+
return this.group;
|
|
71
|
+
}
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
export default Endpoint;
|
package/server/handler.js
CHANGED
|
@@ -1,87 +1,113 @@
|
|
|
1
|
-
import { env } from 'tej-env';
|
|
2
|
-
import TejLogger from 'tej-logger';
|
|
3
|
-
import logHttpRequest from '../utils/request-logger.js';
|
|
4
|
-
|
|
5
|
-
import Ammo from './ammo.js';
|
|
6
|
-
import TejError from './error.js';
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* @param {
|
|
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
|
-
|
|
1
|
+
import { env } from 'tej-env';
|
|
2
|
+
import TejLogger from 'tej-logger';
|
|
3
|
+
import logHttpRequest from '../utils/request-logger.js';
|
|
4
|
+
|
|
5
|
+
import Ammo from './ammo.js';
|
|
6
|
+
import TejError from './error.js';
|
|
7
|
+
import targetRegistry from './targets/registry.js';
|
|
8
|
+
|
|
9
|
+
const errorLogger = new TejLogger('Tejas.Exception');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Executes the middleware and handler chain for a given target.
|
|
13
|
+
*
|
|
14
|
+
* @param {Object} target - The target endpoint object.
|
|
15
|
+
* @param {Ammo} ammo - The Ammo instance containing request and response objects.
|
|
16
|
+
* @returns {Promise<void>} A promise that resolves when the chain execution is complete.
|
|
17
|
+
*/
|
|
18
|
+
const executeChain = async (target, ammo) => {
|
|
19
|
+
let i = 0;
|
|
20
|
+
|
|
21
|
+
const chain = targetRegistry.globalMiddlewares.concat(
|
|
22
|
+
target.getMiddlewares(),
|
|
23
|
+
);
|
|
24
|
+
chain.push(target.getHandler());
|
|
25
|
+
|
|
26
|
+
const next = async () => {
|
|
27
|
+
// Check if response has already been sent (e.g., by passport.authenticate redirect)
|
|
28
|
+
if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const middleware = chain[i];
|
|
33
|
+
i++;
|
|
34
|
+
|
|
35
|
+
const args =
|
|
36
|
+
middleware.length === 3 ? [ammo.req, ammo.res, next] : [ammo, next];
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = await middleware(...args);
|
|
40
|
+
|
|
41
|
+
// Check again after middleware execution (passport might have redirected)
|
|
42
|
+
if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// If middleware returned a promise that resolved, continue chain
|
|
47
|
+
if (result && typeof result.then === 'function') {
|
|
48
|
+
await result;
|
|
49
|
+
// Check one more time after promise resolution
|
|
50
|
+
if (ammo.res.headersSent || ammo.res.writableEnded || ammo.res.finished) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
// Only handle error if response hasn't been sent
|
|
56
|
+
if (!ammo.res.headersSent && !ammo.res.writableEnded && !ammo.res.finished) {
|
|
57
|
+
errorHandler(ammo, err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
await next();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Handles errors by logging them and sending an appropriate response.
|
|
67
|
+
*
|
|
68
|
+
* @param {Ammo} ammo - The Ammo instance containing request and response objects.
|
|
69
|
+
* @param {Error} err - The error object to handle.
|
|
70
|
+
*/
|
|
71
|
+
const errorHandler = (ammo, err) => {
|
|
72
|
+
if (env('LOG_EXCEPTIONS')) errorLogger.error(err);
|
|
73
|
+
|
|
74
|
+
if (err instanceof TejError) return ammo.throw(err);
|
|
75
|
+
return ammo.throw(err);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Main request handler function.
|
|
80
|
+
*
|
|
81
|
+
* @param {http.IncomingMessage} req - The HTTP request object.
|
|
82
|
+
* @param {http.ServerResponse} res - The HTTP response object.
|
|
83
|
+
* @returns {Promise<void>} A promise that resolves when the request handling is complete.
|
|
84
|
+
*/
|
|
85
|
+
const handler = async (req, res) => {
|
|
86
|
+
const url = req.url.split('?')[0];
|
|
87
|
+
const match = targetRegistry.aim(url);
|
|
88
|
+
const ammo = new Ammo(req, res);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
if (match && match.target) {
|
|
92
|
+
await ammo.enhance();
|
|
93
|
+
|
|
94
|
+
// Add route parameters to ammo.payload
|
|
95
|
+
if (match.params && Object.keys(match.params).length > 0) {
|
|
96
|
+
Object.assign(ammo.payload, match.params);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (env('LOG_HTTP_REQUESTS')) logHttpRequest(ammo);
|
|
100
|
+
await executeChain(match.target, ammo);
|
|
101
|
+
} else {
|
|
102
|
+
if (req.url === '/') {
|
|
103
|
+
ammo.defaultEntry();
|
|
104
|
+
} else {
|
|
105
|
+
errorHandler(ammo, new TejError(404, `URL not found: ${url}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
errorHandler(ammo, err);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default handler;
|
package/server/target.js
CHANGED
|
@@ -3,8 +3,7 @@ import TejLogger from 'tej-logger';
|
|
|
3
3
|
import isMiddlewareValid from './targets/middleware-validator.js';
|
|
4
4
|
import Endpoint from './endpoint.js';
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
const targetRegistry = new TargetRegistry();
|
|
6
|
+
import targetRegistry from './targets/registry.js';
|
|
8
7
|
|
|
9
8
|
const logger = new TejLogger('Target');
|
|
10
9
|
|
|
@@ -110,19 +109,61 @@ class Target {
|
|
|
110
109
|
* );
|
|
111
110
|
*/
|
|
112
111
|
register() {
|
|
113
|
-
|
|
114
|
-
if (
|
|
112
|
+
const args = Array.from(arguments);
|
|
113
|
+
if (args.length < 2) {
|
|
114
|
+
logger.error('register(path, [...middlewares], handler) requires at least path and handler. Skipping.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
115
117
|
|
|
116
118
|
const path = args[0];
|
|
117
119
|
const shoot = args[args.length - 1];
|
|
118
|
-
|
|
120
|
+
|
|
121
|
+
if (typeof path !== 'string') {
|
|
122
|
+
logger.error(`register() path must be a string, got ${typeof path}. Skipping.`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (typeof shoot !== 'function') {
|
|
126
|
+
logger.error(`register() last argument (handler) must be a function, got ${typeof shoot}. Skipping.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const second = args[1];
|
|
131
|
+
const isPlainObject = (v) =>
|
|
132
|
+
typeof v === 'object' &&
|
|
133
|
+
v !== null &&
|
|
134
|
+
!Array.isArray(v) &&
|
|
135
|
+
(Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
|
|
136
|
+
const isMetadataObject = isPlainObject(second);
|
|
137
|
+
|
|
138
|
+
let middlewares;
|
|
139
|
+
let metadata = null;
|
|
140
|
+
if (isMetadataObject && args.length >= 3) {
|
|
141
|
+
metadata = second;
|
|
142
|
+
middlewares = args.slice(2, -1);
|
|
143
|
+
} else {
|
|
144
|
+
middlewares = args.slice(1, -1);
|
|
145
|
+
}
|
|
119
146
|
|
|
120
147
|
try {
|
|
121
148
|
const endpoint = new Endpoint();
|
|
122
|
-
endpoint
|
|
123
|
-
|
|
124
|
-
.
|
|
125
|
-
|
|
149
|
+
endpoint.setPath(this.base, path);
|
|
150
|
+
if (!endpoint.getPath()) {
|
|
151
|
+
logger.error(`Invalid path for endpoint "${path}". Skipping.`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
endpoint.setMiddlewares(middlewares);
|
|
155
|
+
endpoint.setHandler(shoot);
|
|
156
|
+
if (!endpoint.getHandler()) {
|
|
157
|
+
logger.error(`Invalid handler for endpoint "${path}". Skipping.`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (metadata !== null) {
|
|
161
|
+
endpoint.setMetadata(metadata);
|
|
162
|
+
}
|
|
163
|
+
const group = targetRegistry.getCurrentSourceGroup();
|
|
164
|
+
if (group != null) {
|
|
165
|
+
endpoint.setGroup(group);
|
|
166
|
+
}
|
|
126
167
|
|
|
127
168
|
targetRegistry.targets.push(endpoint);
|
|
128
169
|
} catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import isMiddlewareValid from './middleware-validator.js';
|
|
2
|
+
import { standardizePath } from './path-validator.js';
|
|
2
3
|
|
|
3
4
|
class TargetRegistry {
|
|
4
5
|
constructor() {
|
|
@@ -11,6 +12,16 @@ class TargetRegistry {
|
|
|
11
12
|
// TODO - Add a default target
|
|
12
13
|
this.targets = [];
|
|
13
14
|
this.globalMiddlewares = [];
|
|
15
|
+
/** Current source group (target file id) set by loader before importing a target file. */
|
|
16
|
+
this._currentSourceGroup = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
setCurrentSourceGroup(group) {
|
|
20
|
+
this._currentSourceGroup = group ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getCurrentSourceGroup() {
|
|
24
|
+
return this._currentSourceGroup;
|
|
14
25
|
}
|
|
15
26
|
|
|
16
27
|
addGlobalMiddleware() {
|
|
@@ -32,13 +43,107 @@ class TargetRegistry {
|
|
|
32
43
|
}
|
|
33
44
|
}
|
|
34
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Matches an endpoint URL to a registered target, supporting parameterized routes.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} endpoint - The endpoint URL to match
|
|
50
|
+
* @returns {Object|null} An object with `target` and `params`, or null if no match
|
|
51
|
+
*/
|
|
35
52
|
aim(endpoint) {
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
const standardizedEndpoint = standardizePath(endpoint);
|
|
54
|
+
|
|
55
|
+
// First, try exact match (most specific)
|
|
56
|
+
const exactMatch = this.targets.find((target) => {
|
|
57
|
+
return target.getPath() === standardizedEndpoint;
|
|
38
58
|
});
|
|
59
|
+
|
|
60
|
+
if (exactMatch) {
|
|
61
|
+
return { target: exactMatch, params: {} };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Then, try parameterized route matching
|
|
65
|
+
for (const target of this.targets) {
|
|
66
|
+
const targetPath = target.getPath();
|
|
67
|
+
const params = this.matchParameterizedRoute(
|
|
68
|
+
targetPath,
|
|
69
|
+
standardizedEndpoint,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (params !== null) {
|
|
73
|
+
return { target, params };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
39
78
|
}
|
|
40
79
|
|
|
41
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Matches a parameterized route pattern against an actual URL.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} pattern - The route pattern (e.g., '/api/categories/:id')
|
|
84
|
+
* @param {string} url - The actual URL to match (e.g., '/api/categories/123')
|
|
85
|
+
* @returns {Object|null} An object with extracted parameters, or null if no match
|
|
86
|
+
*/
|
|
87
|
+
matchParameterizedRoute(pattern, url) {
|
|
88
|
+
// Handle root path case
|
|
89
|
+
if (pattern === '/' && url === '/') {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Split both pattern and URL into segments
|
|
94
|
+
const patternSegments = pattern.split('/').filter((s) => s.length > 0);
|
|
95
|
+
const urlSegments = url.split('/').filter((s) => s.length > 0);
|
|
96
|
+
|
|
97
|
+
// Must have same number of segments
|
|
98
|
+
if (patternSegments.length !== urlSegments.length) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If both are empty (root paths), they match
|
|
103
|
+
if (patternSegments.length === 0 && urlSegments.length === 0) {
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const params = {};
|
|
108
|
+
|
|
109
|
+
// Match each segment
|
|
110
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
111
|
+
const patternSegment = patternSegments[i];
|
|
112
|
+
const urlSegment = urlSegments[i];
|
|
113
|
+
|
|
114
|
+
// If it's a parameter (starts with :)
|
|
115
|
+
if (patternSegment.startsWith(':')) {
|
|
116
|
+
const paramName = patternSegment.slice(1); // Remove the ':'
|
|
117
|
+
params[paramName] = urlSegment;
|
|
118
|
+
} else if (patternSegment !== urlSegment) {
|
|
119
|
+
// If it's not a parameter and doesn't match, no match
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return params;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get all registered endpoints.
|
|
129
|
+
*
|
|
130
|
+
* @param {boolean|{ detailed?: boolean, grouped?: boolean }} [options] - If boolean, treated as grouped (backward compat).
|
|
131
|
+
* If object: detailed=true returns full metadata per endpoint; grouped=true returns paths grouped by first segment.
|
|
132
|
+
* @returns {string[]|Object|Array<{ path: string, metadata: object|null, handler: function }>}
|
|
133
|
+
*/
|
|
134
|
+
getAllEndpoints(options = {}) {
|
|
135
|
+
const grouped =
|
|
136
|
+
typeof options === 'boolean' ? options : (options && options.grouped);
|
|
137
|
+
const detailed =
|
|
138
|
+
typeof options === 'object' && options && options.detailed === true;
|
|
139
|
+
|
|
140
|
+
if (detailed) {
|
|
141
|
+
return this.targets.map((t) => ({
|
|
142
|
+
path: t.getPath(),
|
|
143
|
+
metadata: t.getMetadata(),
|
|
144
|
+
handler: t.getHandler(),
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
42
147
|
if (grouped) {
|
|
43
148
|
return this.targets.reduce((acc, target) => {
|
|
44
149
|
const group = target.getPath().split('/')[1];
|
|
@@ -46,10 +151,10 @@ class TargetRegistry {
|
|
|
46
151
|
acc[group].push(target.getPath());
|
|
47
152
|
return acc;
|
|
48
153
|
}, {});
|
|
49
|
-
} else {
|
|
50
|
-
return this.targets.map((target) => target.getPath());
|
|
51
154
|
}
|
|
155
|
+
return this.targets.map((target) => target.getPath());
|
|
52
156
|
}
|
|
53
157
|
}
|
|
54
158
|
|
|
55
|
-
|
|
159
|
+
const targetRegistry = new TargetRegistry();
|
|
160
|
+
export default targetRegistry;
|