@valkyrjaio/sindri 26.1.3 → 26.3.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/bin/sindri.cjs +580 -309
- package/package.json +2 -2
- package/src/Sindri/Ast/Abstract/AstReader.ts +80 -0
- package/src/Sindri/Ast/CliRouteAttributeReader.ts +11 -11
- package/src/Sindri/Ast/ComponentProviderReader.ts +9 -5
- package/src/Sindri/Ast/ConfigReader.ts +14 -6
- package/src/Sindri/Ast/Contract/HttpRouteMiddlewareReaderContract.ts +1 -1
- package/src/Sindri/Ast/Data/CliRouteData.ts +1 -1
- package/src/Sindri/Ast/Data/Contract/CliRouteDataContract.ts +1 -1
- package/src/Sindri/Ast/Data/Contract/HttpRouteDataContract.ts +1 -1
- package/src/Sindri/Ast/Data/HttpRouteData.ts +1 -1
- package/src/Sindri/Ast/HttpRouteAttributeReader.ts +2 -2
- package/src/Sindri/Ast/HttpRouteMiddlewareReader.ts +9 -9
- package/src/Sindri/Ast/RouteProviderReader.ts +24 -3
- package/src/Sindri/Constant/SindriInfo.ts +2 -2
- package/src/Sindri/Generate/Abstract/GenerateDataFromAst.ts +66 -13
- package/src/Sindri/Generator/Abstract/AstFileGenerator.ts +13 -0
- package/src/Sindri/Generator/Ast/Cli/AstCliDataFileGenerator.ts +47 -11
- package/src/Sindri/Generator/Ast/Container/AstContainerDataFileGenerator.ts +4 -9
- package/src/Sindri/Generator/Ast/Event/AstEventDataFileGenerator.ts +13 -7
- package/src/Sindri/Generator/Ast/Http/AstHttpDataFileGenerator.ts +176 -12
- package/src/Sindri/Generator/Cli/Contract/CliDataFileGeneratorContract.ts +10 -0
- package/src/Sindri/Generator/Container/Contract/ContainerDataFileGeneratorContract.ts +3 -0
- package/src/Sindri/Generator/Http/Contract/HttpDataFileGeneratorContract.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valkyrjaio/sindri",
|
|
3
|
-
"version": "26.
|
|
3
|
+
"version": "26.3.0",
|
|
4
4
|
"description": "The Valkyrja Sindri build tool.",
|
|
5
5
|
"homepage": "https://www.valkyrja.io/sindri",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=22"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@valkyrjaio/valkyrja": "^26.
|
|
41
|
+
"@valkyrjaio/valkyrja": "^26.3.0",
|
|
42
42
|
"ts-morph": "^28.0.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
@@ -141,6 +141,86 @@ export abstract class AstReader {
|
|
|
141
141
|
return classes;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a `new X()` or bare `X` identifier element to the absolute file
|
|
146
|
+
* path of the class it references, using the current file's import map.
|
|
147
|
+
*
|
|
148
|
+
* Returns undefined when the element is neither form, or when the name is
|
|
149
|
+
* not imported via a relative specifier (e.g. framework classes imported
|
|
150
|
+
* from a package — those are intentionally skipped by the provider walk).
|
|
151
|
+
*/
|
|
152
|
+
protected resolveElementToFilePath(
|
|
153
|
+
element: ts.Expression,
|
|
154
|
+
useMap: Record<string, string>,
|
|
155
|
+
currentFilePath: string,
|
|
156
|
+
): string | undefined {
|
|
157
|
+
let name: string | undefined;
|
|
158
|
+
|
|
159
|
+
if (ts.isNewExpression(element)) {
|
|
160
|
+
name = ts.isIdentifier(element.expression) ? element.expression.text : undefined;
|
|
161
|
+
} else if (ts.isIdentifier(element)) {
|
|
162
|
+
name = element.text;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (name === undefined) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const resolved = this.resolveImportToFilePath(name, useMap, currentFilePath);
|
|
170
|
+
|
|
171
|
+
return resolved !== '' ? resolved : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Extract the absolute file paths of the classes referenced by a method
|
|
176
|
+
* returning [new A(), new B(), ...] (or [A, B, ...]).
|
|
177
|
+
*
|
|
178
|
+
* Unlike {@link extractClassListFromValues} (which returns bare class
|
|
179
|
+
* names), this resolves each reference through the import map so provider
|
|
180
|
+
* classes are located unambiguously — critical when the same short class
|
|
181
|
+
* name exists in more than one component tree (e.g. an Http and a Cli
|
|
182
|
+
* `ComponentProvider`).
|
|
183
|
+
*/
|
|
184
|
+
protected extractClassPathListFromValues(
|
|
185
|
+
method: MethodDeclaration | undefined,
|
|
186
|
+
useMap: Record<string, string>,
|
|
187
|
+
currentFilePath: string,
|
|
188
|
+
): string[] {
|
|
189
|
+
if (method === undefined) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const array = this.findReturnedArray(method);
|
|
194
|
+
|
|
195
|
+
if (array === undefined) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return this.extractClassPathListFromArrayExpr(array, useMap, currentFilePath);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Extract the absolute file paths of the classes referenced by the elements
|
|
204
|
+
* of an array literal, using the current file's import map.
|
|
205
|
+
*/
|
|
206
|
+
protected extractClassPathListFromArrayExpr(
|
|
207
|
+
array: ts.ArrayLiteralExpression,
|
|
208
|
+
useMap: Record<string, string>,
|
|
209
|
+
currentFilePath: string,
|
|
210
|
+
): string[] {
|
|
211
|
+
const paths: string[] = [];
|
|
212
|
+
|
|
213
|
+
for (const element of array.elements) {
|
|
214
|
+
const resolved = this.resolveElementToFilePath(element, useMap, currentFilePath);
|
|
215
|
+
|
|
216
|
+
if (resolved !== undefined) {
|
|
217
|
+
paths.push(resolved);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return paths;
|
|
222
|
+
}
|
|
223
|
+
|
|
144
224
|
/**
|
|
145
225
|
* Extract service ID strings from keys of an object-returning method.
|
|
146
226
|
*
|
|
@@ -73,7 +73,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
const updatedName = this.updateName(name, method, useMap, currentFilePath, currentClass);
|
|
76
|
-
const [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware,
|
|
76
|
+
const [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware, processExitingMiddleware] =
|
|
77
77
|
this.updateMiddleware(
|
|
78
78
|
method,
|
|
79
79
|
useMap,
|
|
@@ -93,7 +93,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
93
93
|
routeMatchedMiddleware,
|
|
94
94
|
routeDispatchedMiddleware,
|
|
95
95
|
throwableCaughtMiddleware,
|
|
96
|
-
|
|
96
|
+
processExitingMiddleware,
|
|
97
97
|
this.parameterReader.updateArguments(method, useMap, currentFilePath, currentClass),
|
|
98
98
|
this.parameterReader.updateOptions(method, useMap, currentFilePath, currentClass),
|
|
99
99
|
);
|
|
@@ -130,7 +130,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
130
130
|
routeMatchedMiddleware: string[],
|
|
131
131
|
routeDispatchedMiddleware: string[],
|
|
132
132
|
throwableCaughtMiddleware: string[],
|
|
133
|
-
|
|
133
|
+
processExitingMiddleware: string[],
|
|
134
134
|
): [string[], string[], string[], string[]] {
|
|
135
135
|
for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, currentFilePath)) {
|
|
136
136
|
const mwName = this.extractExprValue(
|
|
@@ -144,7 +144,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
[routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware,
|
|
147
|
+
[routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware, processExitingMiddleware] =
|
|
148
148
|
this.classifyMiddleware(
|
|
149
149
|
mwName,
|
|
150
150
|
useMap,
|
|
@@ -152,11 +152,11 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
152
152
|
routeMatchedMiddleware,
|
|
153
153
|
routeDispatchedMiddleware,
|
|
154
154
|
throwableCaughtMiddleware,
|
|
155
|
-
|
|
155
|
+
processExitingMiddleware,
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
return [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware,
|
|
159
|
+
return [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware, processExitingMiddleware];
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
protected classifyMiddleware(
|
|
@@ -166,7 +166,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
166
166
|
routeMatchedMiddleware: string[],
|
|
167
167
|
routeDispatchedMiddleware: string[],
|
|
168
168
|
throwableCaughtMiddleware: string[],
|
|
169
|
-
|
|
169
|
+
processExitingMiddleware: string[],
|
|
170
170
|
): [string[], string[], string[], string[]] {
|
|
171
171
|
if (this.classImplementsInterface(mwName, 'RouteMatchedMiddlewareContract', useMap, currentFilePath)) {
|
|
172
172
|
routeMatchedMiddleware = [...routeMatchedMiddleware, mwName];
|
|
@@ -180,11 +180,11 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
180
180
|
throwableCaughtMiddleware = [...throwableCaughtMiddleware, mwName];
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
if (this.classImplementsInterface(mwName, '
|
|
184
|
-
|
|
183
|
+
if (this.classImplementsInterface(mwName, 'ProcessExitingMiddlewareContract', useMap, currentFilePath)) {
|
|
184
|
+
processExitingMiddleware = [...processExitingMiddleware, mwName];
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
return [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware,
|
|
187
|
+
return [routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware, processExitingMiddleware];
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
protected buildRouteExpr(data: CliRouteData): ts.Expression {
|
|
@@ -213,7 +213,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
213
213
|
this.buildClassArrayExpr(data.routeMatchedMiddleware),
|
|
214
214
|
this.buildClassArrayExpr(data.routeDispatchedMiddleware),
|
|
215
215
|
this.buildClassArrayExpr(data.throwableCaughtMiddleware),
|
|
216
|
-
this.buildClassArrayExpr(data.
|
|
216
|
+
this.buildClassArrayExpr(data.processExitingMiddleware),
|
|
217
217
|
];
|
|
218
218
|
}
|
|
219
219
|
}
|
|
@@ -30,12 +30,16 @@ export class ComponentProviderReader extends AstReader implements ComponentProvi
|
|
|
30
30
|
const useMap = this.buildUseMap(sourceFile);
|
|
31
31
|
const methods = this.indexMethods(classDecl);
|
|
32
32
|
|
|
33
|
+
// Provider references are resolved through the file's import map to
|
|
34
|
+
// absolute paths (not bare names) so the walk locates each provider
|
|
35
|
+
// unambiguously — the same short name (e.g. `ComponentProvider`,
|
|
36
|
+
// `DataServiceProvider`) exists in both the Http and Cli trees.
|
|
33
37
|
return new ComponentProviderResult(
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
38
|
-
this.
|
|
38
|
+
this.extractClassPathListFromValues(methods[ComponentProviderReader.METHOD_COMPONENT], useMap, filePath),
|
|
39
|
+
this.extractClassPathListFromValues(methods[ComponentProviderReader.METHOD_CONTAINER], useMap, filePath),
|
|
40
|
+
this.extractClassPathListFromValues(methods[ComponentProviderReader.METHOD_EVENT], useMap, filePath),
|
|
41
|
+
this.extractClassPathListFromValues(methods[ComponentProviderReader.METHOD_CLI], useMap, filePath),
|
|
42
|
+
this.extractClassPathListFromValues(methods[ComponentProviderReader.METHOD_HTTP], useMap, filePath),
|
|
39
43
|
);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
@@ -190,21 +190,29 @@ export class ConfigReader extends AstReader implements ConfigReaderContract {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
|
-
* Extract
|
|
194
|
-
*
|
|
193
|
+
* Extract the absolute file paths of the configured provider classes.
|
|
194
|
+
*
|
|
195
|
+
* The providers array is the first array-literal argument after the
|
|
196
|
+
* `dataNamespace` (index 8) — every preceding argument is a scalar. Its
|
|
197
|
+
* exact position varies by config type (index 9 in `HttpConfig`, index 11
|
|
198
|
+
* in `CliConfig` after `applicationName`/`defaultCommandName`), so the
|
|
199
|
+
* array is located by scanning rather than by a fixed index.
|
|
200
|
+
*
|
|
201
|
+
* Each element (`new ComponentProvider()` or a bare `ComponentProvider`
|
|
202
|
+
* identifier) is resolved through the config file's import map to an
|
|
203
|
+
* absolute path, so the walk locates the right provider even when the same
|
|
204
|
+
* short name exists in multiple trees.
|
|
195
205
|
*/
|
|
196
206
|
protected extractProvidersFromArgs(
|
|
197
207
|
args: ts.NodeArray<ts.Expression>,
|
|
198
208
|
useMap: Record<string, string>,
|
|
199
209
|
filePath: string,
|
|
200
210
|
): string[] {
|
|
201
|
-
|
|
202
|
-
// It's typically at index 11 in CliConfig
|
|
203
|
-
for (let i = 10; i < args.length; i++) {
|
|
211
|
+
for (let i = 9; i < args.length; i++) {
|
|
204
212
|
const arg = args[i];
|
|
205
213
|
|
|
206
214
|
if (arg !== undefined && ts.isArrayLiteralExpression(arg)) {
|
|
207
|
-
return this.
|
|
215
|
+
return this.extractClassPathListFromArrayExpr(arg, useMap, filePath);
|
|
208
216
|
}
|
|
209
217
|
}
|
|
210
218
|
|
|
@@ -38,7 +38,7 @@ export interface HttpRouteMiddlewareReaderContract {
|
|
|
38
38
|
routeDispatchedMiddleware: string[],
|
|
39
39
|
throwableCaughtMiddleware: string[],
|
|
40
40
|
sendingResponseMiddleware: string[],
|
|
41
|
-
|
|
41
|
+
responseSentMiddleware: string[],
|
|
42
42
|
): [string[], string[], string[], string[], string[]];
|
|
43
43
|
|
|
44
44
|
updateRequestStruct(
|
|
@@ -23,7 +23,7 @@ export class CliRouteData implements CliRouteDataContract {
|
|
|
23
23
|
readonly routeMatchedMiddleware: readonly string[] = [],
|
|
24
24
|
readonly routeDispatchedMiddleware: readonly string[] = [],
|
|
25
25
|
readonly throwableCaughtMiddleware: readonly string[] = [],
|
|
26
|
-
readonly
|
|
26
|
+
readonly processExitingMiddleware: readonly string[] = [],
|
|
27
27
|
args: readonly CliArgumentParameterData[] = [],
|
|
28
28
|
readonly options: readonly CliOptionParameterData[] = [],
|
|
29
29
|
) {
|
|
@@ -19,7 +19,7 @@ export interface CliRouteDataContract {
|
|
|
19
19
|
readonly routeMatchedMiddleware: readonly string[];
|
|
20
20
|
readonly routeDispatchedMiddleware: readonly string[];
|
|
21
21
|
readonly throwableCaughtMiddleware: readonly string[];
|
|
22
|
-
readonly
|
|
22
|
+
readonly processExitingMiddleware: readonly string[];
|
|
23
23
|
readonly arguments: readonly CliArgumentParameterDataContract[];
|
|
24
24
|
readonly options: readonly CliOptionParameterDataContract[];
|
|
25
25
|
}
|
|
@@ -20,7 +20,7 @@ export interface HttpRouteDataContract {
|
|
|
20
20
|
readonly routeDispatchedMiddleware: readonly string[];
|
|
21
21
|
readonly throwableCaughtMiddleware: readonly string[];
|
|
22
22
|
readonly sendingResponseMiddleware: readonly string[];
|
|
23
|
-
readonly
|
|
23
|
+
readonly responseSentMiddleware: readonly string[];
|
|
24
24
|
readonly requestStruct: string | null;
|
|
25
25
|
readonly responseStruct: string | null;
|
|
26
26
|
readonly isDynamic: boolean;
|
|
@@ -21,7 +21,7 @@ export class HttpRouteData implements HttpRouteDataContract {
|
|
|
21
21
|
readonly routeDispatchedMiddleware: readonly string[] = [],
|
|
22
22
|
readonly throwableCaughtMiddleware: readonly string[] = [],
|
|
23
23
|
readonly sendingResponseMiddleware: readonly string[] = [],
|
|
24
|
-
readonly
|
|
24
|
+
readonly responseSentMiddleware: readonly string[] = [],
|
|
25
25
|
readonly requestStruct: string | null = null,
|
|
26
26
|
readonly responseStruct: string | null = null,
|
|
27
27
|
readonly isDynamic: boolean = false,
|
|
@@ -188,7 +188,7 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
188
188
|
routeDispatchedMiddleware,
|
|
189
189
|
throwableCaughtMiddleware,
|
|
190
190
|
sendingResponseMiddleware,
|
|
191
|
-
|
|
191
|
+
responseSentMiddleware,
|
|
192
192
|
] = this.middlewareReader.updateMiddleware(
|
|
193
193
|
method,
|
|
194
194
|
useMap,
|
|
@@ -210,7 +210,7 @@ export class HttpRouteAttributeReader extends RouteAttributeReader implements Ht
|
|
|
210
210
|
routeDispatchedMiddleware,
|
|
211
211
|
throwableCaughtMiddleware,
|
|
212
212
|
sendingResponseMiddleware,
|
|
213
|
-
|
|
213
|
+
responseSentMiddleware,
|
|
214
214
|
this.middlewareReader.updateRequestStruct(method, useMap, currentFilePath, currentClass),
|
|
215
215
|
this.middlewareReader.updateResponseStruct(method, useMap, currentFilePath, currentClass),
|
|
216
216
|
resolvedIsDynamic,
|
|
@@ -75,7 +75,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
75
75
|
routeDispatchedMiddleware: string[],
|
|
76
76
|
throwableCaughtMiddleware: string[],
|
|
77
77
|
sendingResponseMiddleware: string[],
|
|
78
|
-
|
|
78
|
+
responseSentMiddleware: string[],
|
|
79
79
|
): [string[], string[], string[], string[], string[]] {
|
|
80
80
|
for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, namespace)) {
|
|
81
81
|
const mwName = this.extractExprValue(this.getDecoratorArg(decorator, 0), useMap, namespace, currentClass);
|
|
@@ -89,7 +89,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
89
89
|
routeDispatchedMiddleware,
|
|
90
90
|
throwableCaughtMiddleware,
|
|
91
91
|
sendingResponseMiddleware,
|
|
92
|
-
|
|
92
|
+
responseSentMiddleware,
|
|
93
93
|
] = this.classifyMiddleware(
|
|
94
94
|
mwName,
|
|
95
95
|
useMap,
|
|
@@ -98,7 +98,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
98
98
|
routeDispatchedMiddleware,
|
|
99
99
|
throwableCaughtMiddleware,
|
|
100
100
|
sendingResponseMiddleware,
|
|
101
|
-
|
|
101
|
+
responseSentMiddleware,
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -107,7 +107,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
107
107
|
routeDispatchedMiddleware,
|
|
108
108
|
throwableCaughtMiddleware,
|
|
109
109
|
sendingResponseMiddleware,
|
|
110
|
-
|
|
110
|
+
responseSentMiddleware,
|
|
111
111
|
];
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -151,7 +151,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
151
151
|
this.buildClassIdentifierArrayExpr(data.routeDispatchedMiddleware),
|
|
152
152
|
this.buildClassIdentifierArrayExpr(data.throwableCaughtMiddleware),
|
|
153
153
|
this.buildClassIdentifierArrayExpr(data.sendingResponseMiddleware),
|
|
154
|
-
this.buildClassIdentifierArrayExpr(data.
|
|
154
|
+
this.buildClassIdentifierArrayExpr(data.responseSentMiddleware),
|
|
155
155
|
];
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -181,7 +181,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
181
181
|
routeDispatchedMiddleware: string[],
|
|
182
182
|
throwableCaughtMiddleware: string[],
|
|
183
183
|
sendingResponseMiddleware: string[],
|
|
184
|
-
|
|
184
|
+
responseSentMiddleware: string[],
|
|
185
185
|
): [string[], string[], string[], string[], string[]] {
|
|
186
186
|
if (this.classImplementsInterface(mwName, 'RouteMatchedMiddlewareContract', useMap, currentFilePath)) {
|
|
187
187
|
routeMatchedMiddleware = [...routeMatchedMiddleware, mwName];
|
|
@@ -199,8 +199,8 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
199
199
|
sendingResponseMiddleware = [...sendingResponseMiddleware, mwName];
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
if (this.classImplementsInterface(mwName, '
|
|
203
|
-
|
|
202
|
+
if (this.classImplementsInterface(mwName, 'ResponseSentMiddlewareContract', useMap, currentFilePath)) {
|
|
203
|
+
responseSentMiddleware = [...responseSentMiddleware, mwName];
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
return [
|
|
@@ -208,7 +208,7 @@ export class HttpRouteMiddlewareReader extends AstReader implements HttpRouteMid
|
|
|
208
208
|
routeDispatchedMiddleware,
|
|
209
209
|
throwableCaughtMiddleware,
|
|
210
210
|
sendingResponseMiddleware,
|
|
211
|
-
|
|
211
|
+
responseSentMiddleware,
|
|
212
212
|
];
|
|
213
213
|
}
|
|
214
214
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* file that was distributed with this source code.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import { ts } from 'ts-morph';
|
|
11
11
|
|
|
12
12
|
import { AstReader } from './Abstract/AstReader.ts';
|
|
13
13
|
import { RouteProviderResult } from './Data/Result/RouteProviderResult.ts';
|
|
@@ -35,11 +35,32 @@ export class RouteProviderReader extends AstReader implements RouteProviderReade
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Extract the route objects returned by a provider's `getRoutes()` method.
|
|
40
|
+
*
|
|
41
|
+
* TS route providers register routes imperatively — `getRoutes()` returns
|
|
42
|
+
* the concrete `new Route(...)` / `new DynamicRoute(...)` (or CLI
|
|
43
|
+
* `new Route(...)`) instances. Those expressions are returned verbatim for
|
|
44
|
+
* the data-cache generator to emit as route closures and to derive the
|
|
45
|
+
* path/regex lookup maps from. (Attribute/decorator scanning — the other,
|
|
46
|
+
* optional source of routes — is handled separately via
|
|
47
|
+
* {@link readFile}'s controller-class list.)
|
|
48
|
+
*/
|
|
38
49
|
protected extractRoutes(
|
|
39
|
-
|
|
50
|
+
method: ReturnType<typeof this.indexMethods>[string] | undefined,
|
|
40
51
|
_useMap: Record<string, string>,
|
|
41
52
|
_filePath: string,
|
|
42
53
|
): ts.Expression[] {
|
|
43
|
-
|
|
54
|
+
if (method === undefined) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const array = this.findReturnedArray(method);
|
|
59
|
+
|
|
60
|
+
if (array === undefined) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return array.elements.filter((element): element is ts.NewExpression => ts.isNewExpression(element));
|
|
44
65
|
}
|
|
45
66
|
}
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export class SindriInfo {
|
|
11
|
-
static readonly VERSION = '26.
|
|
12
|
-
static readonly VERSION_BUILD_DATE_TIME = '
|
|
11
|
+
static readonly VERSION = '26.3.0' as const;
|
|
12
|
+
static readonly VERSION_BUILD_DATE_TIME = 'July 25 2026 21:11:03 MST' as const;
|
|
13
13
|
static readonly ICON = `\
|
|
14
14
|
██████████
|
|
15
15
|
████ ████
|
|
@@ -211,6 +211,17 @@ export abstract class GenerateDataFromAst {
|
|
|
211
211
|
return '';
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Build the `import { X } from '...'` specifier for a class file, relative
|
|
216
|
+
* to the generated data file's output directory. Used to import the
|
|
217
|
+
* provider/handler classes referenced by the generated data caches.
|
|
218
|
+
*/
|
|
219
|
+
protected relativeSpecifier(fromDir: string, toFile: string): string {
|
|
220
|
+
const relative = path.relative(fromDir, toFile).split(path.sep).join('/');
|
|
221
|
+
|
|
222
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
214
225
|
protected generateContainerData(
|
|
215
226
|
serviceProviders: readonly string[],
|
|
216
227
|
config: ConfigResult,
|
|
@@ -221,6 +232,7 @@ export abstract class GenerateDataFromAst {
|
|
|
221
232
|
.writeMessages();
|
|
222
233
|
|
|
223
234
|
const publishers: Record<string, readonly [string, string]> = {};
|
|
235
|
+
const importMap: Record<string, string> = {};
|
|
224
236
|
|
|
225
237
|
for (const providerClass of serviceProviders) {
|
|
226
238
|
const filePath = this.fqnToFilePath(providerClass, config.namespace, config.dir);
|
|
@@ -231,9 +243,17 @@ export abstract class GenerateDataFromAst {
|
|
|
231
243
|
|
|
232
244
|
const result = this.serviceProviderReader.readFile(filePath);
|
|
233
245
|
|
|
246
|
+
if (Object.keys(result.publishers).length > 0) {
|
|
247
|
+
// The publisher callbacks reference the provider's static methods,
|
|
248
|
+
// so the generated data cache must import the provider class.
|
|
249
|
+
importMap[path.basename(filePath, '.ts')] = this.relativeSpecifier(config.dataPath, filePath);
|
|
250
|
+
}
|
|
251
|
+
|
|
234
252
|
Object.assign(publishers, result.publishers);
|
|
235
253
|
}
|
|
236
254
|
|
|
255
|
+
this.containerGenerator.classImportMap = importMap;
|
|
256
|
+
|
|
237
257
|
const status = this.containerGenerator.generateFile(
|
|
238
258
|
config.dataPath,
|
|
239
259
|
'AppContainerData',
|
|
@@ -297,6 +317,8 @@ export abstract class GenerateDataFromAst {
|
|
|
297
317
|
.writeMessages();
|
|
298
318
|
|
|
299
319
|
const allRoutes: Record<string, ts.Expression> = {};
|
|
320
|
+
const imperativeRoutes: ts.Expression[] = [];
|
|
321
|
+
const importMap: Record<string, string> = {};
|
|
300
322
|
|
|
301
323
|
for (const providerClass of cliRouteProviders) {
|
|
302
324
|
const filePath = this.fqnToFilePath(providerClass, config.namespace, config.dir);
|
|
@@ -307,6 +329,13 @@ export abstract class GenerateDataFromAst {
|
|
|
307
329
|
|
|
308
330
|
const providerResult = this.routeProviderReader.readFile(filePath);
|
|
309
331
|
|
|
332
|
+
if (providerResult.routes.length > 0) {
|
|
333
|
+
// Imperative routes reference the provider's static handlers,
|
|
334
|
+
// so the generated data cache must import the provider class.
|
|
335
|
+
imperativeRoutes.push(...providerResult.routes);
|
|
336
|
+
importMap[path.basename(filePath, '.ts')] = this.relativeSpecifier(config.dataPath, filePath);
|
|
337
|
+
}
|
|
338
|
+
|
|
310
339
|
for (const controllerClass of providerResult.controllerClasses) {
|
|
311
340
|
const controllerPath = this.fqnToFilePath(controllerClass, config.namespace, config.dir);
|
|
312
341
|
|
|
@@ -320,12 +349,17 @@ export abstract class GenerateDataFromAst {
|
|
|
320
349
|
}
|
|
321
350
|
}
|
|
322
351
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
352
|
+
this.cliGenerator.classImportMap = importMap;
|
|
353
|
+
|
|
354
|
+
const status =
|
|
355
|
+
imperativeRoutes.length > 0
|
|
356
|
+
? this.cliGenerator.generateFileFromRoutes(
|
|
357
|
+
config.dataPath,
|
|
358
|
+
'AppCliRoutingData',
|
|
359
|
+
config.dataNamespace,
|
|
360
|
+
imperativeRoutes,
|
|
361
|
+
)
|
|
362
|
+
: this.cliGenerator.generateFile(config.dataPath, 'AppCliRoutingData', config.dataNamespace, allRoutes);
|
|
329
363
|
|
|
330
364
|
return this.addMessagesForGenerateStatus(output, status).withAddedMessages(new NewLine()).writeMessages();
|
|
331
365
|
}
|
|
@@ -341,6 +375,8 @@ export abstract class GenerateDataFromAst {
|
|
|
341
375
|
|
|
342
376
|
const allRoutes: Record<string, ts.Expression> = {};
|
|
343
377
|
const allRouteData: Record<string, HttpRouteData> = {};
|
|
378
|
+
const imperativeRoutes: ts.Expression[] = [];
|
|
379
|
+
const importMap: Record<string, string> = {};
|
|
344
380
|
|
|
345
381
|
for (const providerClass of httpRouteProviders) {
|
|
346
382
|
const filePath = this.fqnToFilePath(providerClass, config.namespace, config.dir);
|
|
@@ -351,6 +387,13 @@ export abstract class GenerateDataFromAst {
|
|
|
351
387
|
|
|
352
388
|
const providerResult = this.routeProviderReader.readFile(filePath);
|
|
353
389
|
|
|
390
|
+
if (providerResult.routes.length > 0) {
|
|
391
|
+
// Imperative routes reference the provider's static handlers,
|
|
392
|
+
// so the generated data cache must import the provider class.
|
|
393
|
+
imperativeRoutes.push(...providerResult.routes);
|
|
394
|
+
importMap[path.basename(filePath, '.ts')] = this.relativeSpecifier(config.dataPath, filePath);
|
|
395
|
+
}
|
|
396
|
+
|
|
354
397
|
for (const controllerClass of providerResult.controllerClasses) {
|
|
355
398
|
const controllerPath = this.fqnToFilePath(controllerClass, config.namespace, config.dir);
|
|
356
399
|
|
|
@@ -365,13 +408,23 @@ export abstract class GenerateDataFromAst {
|
|
|
365
408
|
}
|
|
366
409
|
}
|
|
367
410
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
411
|
+
this.httpGenerator.classImportMap = importMap;
|
|
412
|
+
|
|
413
|
+
const status =
|
|
414
|
+
imperativeRoutes.length > 0
|
|
415
|
+
? this.httpGenerator.generateFileFromRoutes(
|
|
416
|
+
config.dataPath,
|
|
417
|
+
'AppHttpRoutingData',
|
|
418
|
+
config.dataNamespace,
|
|
419
|
+
imperativeRoutes,
|
|
420
|
+
)
|
|
421
|
+
: this.httpGenerator.generateFile(
|
|
422
|
+
config.dataPath,
|
|
423
|
+
'AppHttpRoutingData',
|
|
424
|
+
config.dataNamespace,
|
|
425
|
+
allRoutes,
|
|
426
|
+
allRouteData,
|
|
427
|
+
);
|
|
375
428
|
|
|
376
429
|
return this.addMessagesForGenerateStatus(output, status).withAddedMessages(new NewLine()).writeMessages();
|
|
377
430
|
}
|
|
@@ -31,6 +31,19 @@ export abstract class AstFileGenerator {
|
|
|
31
31
|
return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), caseName);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Build the `import { X } from '...';` block for the provider/handler classes
|
|
36
|
+
* a generated data cache references, followed by a trailing newline (or an
|
|
37
|
+
* empty string when there are no such imports).
|
|
38
|
+
*/
|
|
39
|
+
protected buildUserImportsBlock(classImportMap: Record<string, string>): string {
|
|
40
|
+
const userImports = Object.entries(classImportMap)
|
|
41
|
+
.map(([name, specifier]) => `import { ${name} } from '${specifier}';`)
|
|
42
|
+
.join('\n');
|
|
43
|
+
|
|
44
|
+
return userImports ? `${userImports}\n` : '';
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
protected writeFile(directory: string, className: string, data: string): GenerateStatus {
|
|
35
48
|
const filePath = directory.replace(/\/$/, '') + `/${className}.ts`;
|
|
36
49
|
|