@valkyrjaio/sindri 26.5.0 → 26.6.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 +574 -268
- package/package.json +2 -2
- package/src/Sindri/Ast/Abstract/AstReader.ts +185 -2
- package/src/Sindri/Ast/CliRouteAttributeReader.ts +100 -14
- package/src/Sindri/Ast/CliRouteParameterReader.ts +42 -23
- package/src/Sindri/Ast/Contract/HttpRouteMiddlewareReaderContract.ts +3 -7
- package/src/Sindri/Ast/Contract/HttpRouteParameterReaderContract.ts +1 -4
- package/src/Sindri/Ast/Data/Contract/HttpParameterDataContract.ts +2 -0
- package/src/Sindri/Ast/Data/HttpParameterData.ts +1 -0
- package/src/Sindri/Ast/Data/Result/CliRouteAttributeResult.ts +5 -1
- package/src/Sindri/Ast/Data/Result/HttpRouteAttributeResult.ts +2 -0
- package/src/Sindri/Ast/HttpRouteAttributeReader.ts +178 -48
- package/src/Sindri/Ast/HttpRouteMiddlewareReader.ts +36 -15
- package/src/Sindri/Ast/HttpRouteParameterReader.ts +48 -106
- package/src/Sindri/Constant/SindriInfo.ts +2 -2
- package/src/Sindri/Generate/Abstract/GenerateDataFromAst.ts +37 -24
- package/src/Sindri/Generator/Ast/Cli/AstCliDataFileGenerator.ts +19 -3
- package/src/Sindri/Generator/Ast/Http/AstHttpDataFileGenerator.ts +81 -42
- package/src/Sindri/Generator/Cli/Contract/CliDataFileGeneratorContract.ts +8 -0
- package/src/Sindri/Generator/Http/Contract/HttpDataFileGeneratorContract.ts +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valkyrjaio/sindri",
|
|
3
|
-
"version": "26.
|
|
3
|
+
"version": "26.6.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.6.0",
|
|
42
42
|
"ts-morph": "^28.0.0"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
@@ -612,6 +612,153 @@ export abstract class AstReader {
|
|
|
612
612
|
return decorator.getArguments()[position];
|
|
613
613
|
}
|
|
614
614
|
|
|
615
|
+
// -------------------------------------------------------------------------
|
|
616
|
+
// Object-literal decorator helpers — the shipped decorators accept a single
|
|
617
|
+
// options object (`@Route({ path, name, ... })`) rather than positional args.
|
|
618
|
+
// -------------------------------------------------------------------------
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Return the decorator's first argument when it is an object literal, else undefined.
|
|
622
|
+
*/
|
|
623
|
+
protected getDecoratorObjectArg(decorator: Decorator): ts.ObjectLiteralExpression | undefined {
|
|
624
|
+
const arg = this.getDecoratorArg(decorator, 0);
|
|
625
|
+
|
|
626
|
+
if (arg === undefined) {
|
|
627
|
+
return undefined;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const tsNode = arg.compilerNode;
|
|
631
|
+
|
|
632
|
+
return ts.isObjectLiteralExpression(tsNode) ? tsNode : undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Return the value expression of the named property on an object literal, or
|
|
637
|
+
* undefined when the property is absent (or is not a plain property assignment).
|
|
638
|
+
*/
|
|
639
|
+
protected getObjectProp(obj: ts.ObjectLiteralExpression, key: string): ts.Expression | undefined {
|
|
640
|
+
for (const prop of obj.properties) {
|
|
641
|
+
if (ts.isPropertyAssignment(prop) && this.propertyNameToString(prop.name) === key) {
|
|
642
|
+
return prop.initializer;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Resolve a property name node (identifier or string literal) to its text.
|
|
651
|
+
*/
|
|
652
|
+
protected propertyNameToString(name: ts.PropertyName): string | undefined {
|
|
653
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
654
|
+
return name.text;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Extract a string-valued object property, falling back to defaultValue.
|
|
662
|
+
*/
|
|
663
|
+
protected getObjectStringProp(
|
|
664
|
+
obj: ts.ObjectLiteralExpression,
|
|
665
|
+
key: string,
|
|
666
|
+
useMap: Record<string, string>,
|
|
667
|
+
currentFilePath: string,
|
|
668
|
+
currentClass: string,
|
|
669
|
+
defaultValue: string = '',
|
|
670
|
+
): string {
|
|
671
|
+
const value = this.extractExprValue(this.getObjectProp(obj, key), useMap, currentFilePath, currentClass);
|
|
672
|
+
|
|
673
|
+
return typeof value === 'string' ? value : defaultValue;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Extract a boolean-valued object property, falling back to defaultValue.
|
|
678
|
+
*/
|
|
679
|
+
protected getObjectBoolProp(
|
|
680
|
+
obj: ts.ObjectLiteralExpression,
|
|
681
|
+
key: string,
|
|
682
|
+
useMap: Record<string, string>,
|
|
683
|
+
currentFilePath: string,
|
|
684
|
+
currentClass: string,
|
|
685
|
+
defaultValue: boolean = false,
|
|
686
|
+
): boolean {
|
|
687
|
+
const value = this.extractExprValue(this.getObjectProp(obj, key), useMap, currentFilePath, currentClass);
|
|
688
|
+
|
|
689
|
+
return typeof value === 'boolean' ? value : defaultValue;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Extract a class-list object property (an array literal of class references),
|
|
694
|
+
* returning an empty array when the property is absent or not an array.
|
|
695
|
+
*/
|
|
696
|
+
protected getObjectClassListProp(
|
|
697
|
+
obj: ts.ObjectLiteralExpression,
|
|
698
|
+
key: string,
|
|
699
|
+
useMap: Record<string, string>,
|
|
700
|
+
currentFilePath: string,
|
|
701
|
+
currentClass: string,
|
|
702
|
+
): string[] {
|
|
703
|
+
const node = this.getObjectProp(obj, key);
|
|
704
|
+
|
|
705
|
+
if (node === undefined || !ts.isArrayLiteralExpression(node)) {
|
|
706
|
+
return [];
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return this.extractClassListFromArrayExpr(node, useMap, currentFilePath, currentClass);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Extract a string-list object property (an array literal of string values),
|
|
714
|
+
* returning an empty array when the property is absent or not an array.
|
|
715
|
+
*/
|
|
716
|
+
protected getObjectStringListProp(
|
|
717
|
+
obj: ts.ObjectLiteralExpression,
|
|
718
|
+
key: string,
|
|
719
|
+
useMap: Record<string, string>,
|
|
720
|
+
currentFilePath: string,
|
|
721
|
+
currentClass: string,
|
|
722
|
+
): string[] {
|
|
723
|
+
const node = this.getObjectProp(obj, key);
|
|
724
|
+
|
|
725
|
+
if (node === undefined || !ts.isArrayLiteralExpression(node)) {
|
|
726
|
+
return [];
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const values: string[] = [];
|
|
730
|
+
|
|
731
|
+
for (const element of node.elements) {
|
|
732
|
+
const value = this.extractExprValue(element, useMap, currentFilePath, currentClass);
|
|
733
|
+
|
|
734
|
+
if (typeof value === 'string') {
|
|
735
|
+
values.push(value);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return values;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Extract a handler-reference object property (`[Class, 'method']`), returning
|
|
744
|
+
* undefined when the property is absent or not a two-element handler array.
|
|
745
|
+
*/
|
|
746
|
+
protected getObjectHandlerProp(
|
|
747
|
+
obj: ts.ObjectLiteralExpression,
|
|
748
|
+
key: string,
|
|
749
|
+
useMap: Record<string, string>,
|
|
750
|
+
currentFilePath: string,
|
|
751
|
+
currentClass: string,
|
|
752
|
+
): HandlerData | undefined {
|
|
753
|
+
const node = this.getObjectProp(obj, key);
|
|
754
|
+
|
|
755
|
+
if (node === undefined || !ts.isArrayLiteralExpression(node)) {
|
|
756
|
+
return undefined;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return this.extractHandlerFromTsArray(node, useMap, currentFilePath, currentClass);
|
|
760
|
+
}
|
|
761
|
+
|
|
615
762
|
/**
|
|
616
763
|
* Convert a simple expression node to a scalar value or HandlerData.
|
|
617
764
|
*/
|
|
@@ -692,7 +839,12 @@ export abstract class AstReader {
|
|
|
692
839
|
return undefined;
|
|
693
840
|
}
|
|
694
841
|
|
|
695
|
-
const classValue = this.extractExprValue(
|
|
842
|
+
const classValue = this.extractExprValue(
|
|
843
|
+
this.unwrapClassThunk(array.elements[0]),
|
|
844
|
+
useMap,
|
|
845
|
+
currentFilePath,
|
|
846
|
+
currentClass,
|
|
847
|
+
);
|
|
696
848
|
const methodValue = this.extractExprValue(array.elements[1], useMap, currentFilePath, currentClass);
|
|
697
849
|
|
|
698
850
|
if (typeof classValue !== 'string' || classValue === '') {
|
|
@@ -706,6 +858,35 @@ export abstract class AstReader {
|
|
|
706
858
|
return new HandlerData(classValue, methodValue);
|
|
707
859
|
}
|
|
708
860
|
|
|
861
|
+
/**
|
|
862
|
+
* Unwrap a class **thunk** to the class expression it returns —
|
|
863
|
+
* `() => HomeController` becomes `HomeController`.
|
|
864
|
+
*
|
|
865
|
+
* Every class reference in a decorator argument is authored as a thunk —
|
|
866
|
+
* `[() => SomeClass, 'method']` for a handler, `@Middleware(() => SomeClass)`
|
|
867
|
+
* for middleware — rather than as a bare class name. A TC39 Stage-3
|
|
868
|
+
* decorator runs while the class binding is still in its temporal dead zone,
|
|
869
|
+
* so naming the class directly throws at class-definition time on a circular
|
|
870
|
+
* or self-referential import. The thunk is purely a runtime deferral: the
|
|
871
|
+
* emitted cache is unchanged (`HttpRouteProvider.versionHandler`), so the
|
|
872
|
+
* reader simply looks through it. A bare class expression is returned
|
|
873
|
+
* untouched, and a block-bodied arrow (`() => { … }`) is left alone so it
|
|
874
|
+
* falls through to the usual "not a class reference" handling.
|
|
875
|
+
*/
|
|
876
|
+
protected unwrapClassThunk(node: Node | ts.Node | undefined): Node | ts.Node | undefined {
|
|
877
|
+
if (node === undefined) {
|
|
878
|
+
return node;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const tsNode = Node.isNode(node) ? node.compilerNode : node;
|
|
882
|
+
|
|
883
|
+
if (!ts.isArrowFunction(tsNode) || tsNode.parameters.length !== 0) {
|
|
884
|
+
return node;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return ts.isBlock(tsNode.body) ? node : tsNode.body;
|
|
888
|
+
}
|
|
889
|
+
|
|
709
890
|
protected extractClassListFromArrayExpr(
|
|
710
891
|
array: ts.ArrayLiteralExpression,
|
|
711
892
|
useMap: Record<string, string>,
|
|
@@ -715,7 +896,9 @@ export abstract class AstReader {
|
|
|
715
896
|
const classes: string[] = [];
|
|
716
897
|
|
|
717
898
|
for (const element of array.elements) {
|
|
718
|
-
|
|
899
|
+
// A class list inside a decorator argument holds thunked references
|
|
900
|
+
// (`middleware: [() => AuthMiddleware]`), so look through the thunk.
|
|
901
|
+
const value = this.extractExprValue(this.unwrapClassThunk(element), useMap, currentFilePath, currentClass);
|
|
719
902
|
|
|
720
903
|
if (typeof value === 'string' && value !== '') {
|
|
721
904
|
classes.push(value);
|
|
@@ -18,6 +18,7 @@ import type { Decorator, MethodDeclaration } from 'ts-morph';
|
|
|
18
18
|
|
|
19
19
|
import type { CliRouteAttributeReaderContract } from './Contract/CliRouteAttributeReaderContract.ts';
|
|
20
20
|
import type { CliRouteParameterReaderContract } from './Contract/CliRouteParameterReaderContract.ts';
|
|
21
|
+
import type { HandlerData } from './Data/HandlerData.ts';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Scans a CLI controller class file for @Route and related sub-decorators and
|
|
@@ -40,6 +41,7 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
40
41
|
|
|
41
42
|
const { classDecl, useMap, currentClass } = context;
|
|
42
43
|
const routes: Record<string, ts.Expression> = {};
|
|
44
|
+
const routeData: Record<string, CliRouteData> = {};
|
|
43
45
|
|
|
44
46
|
for (const method of classDecl.getMethods()) {
|
|
45
47
|
for (const decorator of this.findDecoratorsOnNode(method, 'Route', useMap, filePath)) {
|
|
@@ -47,11 +49,14 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
47
49
|
|
|
48
50
|
if (data !== null) {
|
|
49
51
|
routes[data.name] = this.buildRouteExpr(data);
|
|
52
|
+
routeData[data.name] = data;
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
const importMap = this.buildImportMap(routeData, useMap, filePath, currentClass);
|
|
58
|
+
|
|
59
|
+
return new CliRouteAttributeResult(routes, importMap);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
protected getRouteHandlerDecoratorName(): string {
|
|
@@ -65,8 +70,14 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
65
70
|
currentFilePath: string,
|
|
66
71
|
currentClass: string,
|
|
67
72
|
): CliRouteData | null {
|
|
68
|
-
const
|
|
69
|
-
|
|
73
|
+
const obj = this.getDecoratorObjectArg(decorator);
|
|
74
|
+
|
|
75
|
+
if (obj === undefined) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
|
|
80
|
+
const description = this.getObjectStringProp(obj, 'description', useMap, currentFilePath, currentClass);
|
|
70
81
|
|
|
71
82
|
if (name === '' || description === '') {
|
|
72
83
|
return null;
|
|
@@ -79,17 +90,14 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
79
90
|
useMap,
|
|
80
91
|
currentFilePath,
|
|
81
92
|
currentClass,
|
|
82
|
-
this.
|
|
83
|
-
this.extractClassListArgFromDecorator(decorator, 5, useMap, currentFilePath, currentClass),
|
|
84
|
-
this.extractClassListArgFromDecorator(decorator, 6, useMap, currentFilePath, currentClass),
|
|
85
|
-
this.extractClassListArgFromDecorator(decorator, 7, useMap, currentFilePath, currentClass),
|
|
93
|
+
this.getObjectClassListProp(obj, 'middleware', useMap, currentFilePath, currentClass),
|
|
86
94
|
);
|
|
87
95
|
|
|
88
96
|
return new CliRouteData(
|
|
89
97
|
updatedName,
|
|
90
98
|
description,
|
|
91
|
-
this.
|
|
92
|
-
null,
|
|
99
|
+
this.resolveHandler(obj, method, useMap, currentFilePath, currentClass),
|
|
100
|
+
this.getObjectHandlerProp(obj, 'helpText', useMap, currentFilePath, currentClass) ?? null,
|
|
93
101
|
routeMatchedMiddleware,
|
|
94
102
|
routeDispatchedMiddleware,
|
|
95
103
|
throwableCaughtMiddleware,
|
|
@@ -99,6 +107,22 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
99
107
|
);
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
protected resolveHandler(
|
|
111
|
+
obj: ts.ObjectLiteralExpression,
|
|
112
|
+
method: MethodDeclaration,
|
|
113
|
+
useMap: Record<string, string>,
|
|
114
|
+
currentFilePath: string,
|
|
115
|
+
currentClass: string,
|
|
116
|
+
): HandlerData {
|
|
117
|
+
const fromObj = this.getObjectHandlerProp(obj, 'handler', useMap, currentFilePath, currentClass);
|
|
118
|
+
|
|
119
|
+
if (fromObj !== undefined) {
|
|
120
|
+
return fromObj;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this.updateHandler(method, useMap, currentFilePath, currentClass);
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
protected updateName(
|
|
103
127
|
name: string,
|
|
104
128
|
method: MethodDeclaration,
|
|
@@ -127,14 +151,30 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
127
151
|
useMap: Record<string, string>,
|
|
128
152
|
currentFilePath: string,
|
|
129
153
|
currentClass: string,
|
|
130
|
-
|
|
131
|
-
routeDispatchedMiddleware: string[],
|
|
132
|
-
throwableCaughtMiddleware: string[],
|
|
133
|
-
processExitingMiddleware: string[],
|
|
154
|
+
middleware: string[],
|
|
134
155
|
): [string[], string[], string[], string[]] {
|
|
156
|
+
let routeMatchedMiddleware: string[] = [];
|
|
157
|
+
let routeDispatchedMiddleware: string[] = [];
|
|
158
|
+
let throwableCaughtMiddleware: string[] = [];
|
|
159
|
+
let processExitingMiddleware: string[] = [];
|
|
160
|
+
|
|
161
|
+
for (const mwName of middleware) {
|
|
162
|
+
[routeMatchedMiddleware, routeDispatchedMiddleware, throwableCaughtMiddleware, processExitingMiddleware] =
|
|
163
|
+
this.classifyMiddleware(
|
|
164
|
+
mwName,
|
|
165
|
+
useMap,
|
|
166
|
+
currentFilePath,
|
|
167
|
+
routeMatchedMiddleware,
|
|
168
|
+
routeDispatchedMiddleware,
|
|
169
|
+
throwableCaughtMiddleware,
|
|
170
|
+
processExitingMiddleware,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
135
174
|
for (const decorator of this.findDecoratorsOnNode(method, 'Middleware', useMap, currentFilePath)) {
|
|
175
|
+
// `@Middleware` takes a thunked class reference (`() => AuthMiddleware`).
|
|
136
176
|
const mwName = this.extractExprValue(
|
|
137
|
-
this.getDecoratorArg(decorator, 0),
|
|
177
|
+
this.unwrapClassThunk(this.getDecoratorArg(decorator, 0)),
|
|
138
178
|
useMap,
|
|
139
179
|
currentFilePath,
|
|
140
180
|
currentClass,
|
|
@@ -216,4 +256,50 @@ export class CliRouteAttributeReader extends RouteAttributeReader implements Cli
|
|
|
216
256
|
this.buildClassArrayExpr(data.processExitingMiddleware),
|
|
217
257
|
];
|
|
218
258
|
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Build the class-name → absolute-file-path map for every handler and
|
|
262
|
+
* help-text class the generated data cache references, so the generator can
|
|
263
|
+
* emit the matching import statements. (Middleware are emitted as string
|
|
264
|
+
* literals and therefore need no import.)
|
|
265
|
+
*/
|
|
266
|
+
protected buildImportMap(
|
|
267
|
+
routeData: Record<string, CliRouteData>,
|
|
268
|
+
useMap: Record<string, string>,
|
|
269
|
+
currentFilePath: string,
|
|
270
|
+
currentClass: string,
|
|
271
|
+
): Record<string, string> {
|
|
272
|
+
const importMap: Record<string, string> = {};
|
|
273
|
+
|
|
274
|
+
for (const data of Object.values(routeData)) {
|
|
275
|
+
if (data.handler !== null) {
|
|
276
|
+
this.addClassImport(importMap, data.handler.class, useMap, currentFilePath, currentClass);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (data.helpText !== null) {
|
|
280
|
+
this.addClassImport(importMap, data.helpText.class, useMap, currentFilePath, currentClass);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return importMap;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
protected addClassImport(
|
|
288
|
+
importMap: Record<string, string>,
|
|
289
|
+
className: string,
|
|
290
|
+
useMap: Record<string, string>,
|
|
291
|
+
currentFilePath: string,
|
|
292
|
+
currentClass: string,
|
|
293
|
+
): void {
|
|
294
|
+
const shortName = className.slice(className.lastIndexOf('\\') + 1);
|
|
295
|
+
|
|
296
|
+
const filePath =
|
|
297
|
+
shortName === currentClass
|
|
298
|
+
? currentFilePath
|
|
299
|
+
: this.resolveImportToFilePath(shortName, useMap, currentFilePath);
|
|
300
|
+
|
|
301
|
+
if (filePath !== '') {
|
|
302
|
+
importMap[shortName] = filePath;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
219
305
|
}
|
|
@@ -28,7 +28,10 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
|
|
|
28
28
|
buildParameterArgs(data: CliRouteData): ts.Expression[] {
|
|
29
29
|
const args: ts.Expression[] = [];
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// The `arguments_` constructor parameter precedes `options`, so an empty
|
|
32
|
+
// arguments array must still be emitted whenever options are present, to
|
|
33
|
+
// keep `options` in its correct positional slot.
|
|
34
|
+
if (data.arguments.length > 0 || data.options.length > 0) {
|
|
32
35
|
args.push(this.buildArgumentListExpr(data.arguments));
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -83,8 +86,14 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
|
|
|
83
86
|
currentFilePath: string,
|
|
84
87
|
currentClass: string,
|
|
85
88
|
): CliArgumentParameterData | null {
|
|
86
|
-
const
|
|
87
|
-
|
|
89
|
+
const obj = this.getDecoratorObjectArg(decorator);
|
|
90
|
+
|
|
91
|
+
if (obj === undefined) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
|
|
96
|
+
const description = this.getObjectStringProp(obj, 'description', useMap, currentFilePath, currentClass);
|
|
88
97
|
|
|
89
98
|
if (name === '' || description === '') {
|
|
90
99
|
return null;
|
|
@@ -93,18 +102,18 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
|
|
|
93
102
|
return new CliArgumentParameterData(
|
|
94
103
|
name,
|
|
95
104
|
description,
|
|
96
|
-
this.
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
this.getObjectStringProp(obj, 'cast', useMap, currentFilePath, currentClass) || null,
|
|
106
|
+
this.getObjectStringProp(
|
|
107
|
+
obj,
|
|
108
|
+
'mode',
|
|
100
109
|
useMap,
|
|
101
110
|
currentFilePath,
|
|
102
111
|
currentClass,
|
|
103
112
|
'Valkyrja\\Cli\\Routing\\Enum\\ArgumentMode::OPTIONAL',
|
|
104
113
|
),
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
this.getObjectStringProp(
|
|
115
|
+
obj,
|
|
116
|
+
'valueMode',
|
|
108
117
|
useMap,
|
|
109
118
|
currentFilePath,
|
|
110
119
|
currentClass,
|
|
@@ -119,8 +128,14 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
|
|
|
119
128
|
currentFilePath: string,
|
|
120
129
|
currentClass: string,
|
|
121
130
|
): CliOptionParameterData | null {
|
|
122
|
-
const
|
|
123
|
-
|
|
131
|
+
const obj = this.getDecoratorObjectArg(decorator);
|
|
132
|
+
|
|
133
|
+
if (obj === undefined) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const name = this.getObjectStringProp(obj, 'name', useMap, currentFilePath, currentClass);
|
|
138
|
+
const description = this.getObjectStringProp(obj, 'description', useMap, currentFilePath, currentClass);
|
|
124
139
|
|
|
125
140
|
if (name === '' || description === '') {
|
|
126
141
|
return null;
|
|
@@ -129,22 +144,22 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
|
|
|
129
144
|
return new CliOptionParameterData(
|
|
130
145
|
name,
|
|
131
146
|
description,
|
|
132
|
-
this.
|
|
133
|
-
this.
|
|
134
|
-
this.
|
|
135
|
-
this.
|
|
136
|
-
this.
|
|
137
|
-
this.
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
this.getObjectStringProp(obj, 'valueDisplayName', useMap, currentFilePath, currentClass),
|
|
148
|
+
this.getObjectStringProp(obj, 'cast', useMap, currentFilePath, currentClass) || null,
|
|
149
|
+
this.getObjectStringProp(obj, 'defaultValue', useMap, currentFilePath, currentClass),
|
|
150
|
+
this.getObjectStringListProp(obj, 'shortNames', useMap, currentFilePath, currentClass),
|
|
151
|
+
this.getObjectStringListProp(obj, 'validValues', useMap, currentFilePath, currentClass),
|
|
152
|
+
this.getObjectStringProp(
|
|
153
|
+
obj,
|
|
154
|
+
'mode',
|
|
140
155
|
useMap,
|
|
141
156
|
currentFilePath,
|
|
142
157
|
currentClass,
|
|
143
158
|
'Valkyrja\\Cli\\Routing\\Enum\\OptionMode::OPTIONAL',
|
|
144
159
|
),
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
|
|
160
|
+
this.getObjectStringProp(
|
|
161
|
+
obj,
|
|
162
|
+
'valueMode',
|
|
148
163
|
useMap,
|
|
149
164
|
currentFilePath,
|
|
150
165
|
currentClass,
|
|
@@ -196,6 +211,10 @@ export class CliRouteParameterReader extends AstReader implements CliRouteParame
|
|
|
196
211
|
args.push(ts.factory.createArrayLiteralExpression([]));
|
|
197
212
|
}
|
|
198
213
|
|
|
214
|
+
// The framework `OptionParameter` constructor carries a runtime-populated
|
|
215
|
+
// `options` array between `validValues` and `mode`; emit an empty array so
|
|
216
|
+
// `mode`/`valueMode` land in their correct positional slots.
|
|
217
|
+
args.push(ts.factory.createArrayLiteralExpression([]));
|
|
199
218
|
args.push(this.buildEnumCaseExpr(data.mode));
|
|
200
219
|
args.push(this.buildEnumCaseExpr(data.valueMode));
|
|
201
220
|
|
|
@@ -14,8 +14,8 @@ import type { MethodDeclaration } from 'ts-morph';
|
|
|
14
14
|
import type { HttpRouteData } from '../Data/HttpRouteData.ts';
|
|
15
15
|
|
|
16
16
|
export interface HttpRouteMiddlewareReaderContract {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
extractObjectRequestMethods(
|
|
18
|
+
obj: ts.ObjectLiteralExpression,
|
|
19
19
|
useMap: Record<string, string>,
|
|
20
20
|
namespace: string,
|
|
21
21
|
currentClass: string,
|
|
@@ -34,11 +34,7 @@ export interface HttpRouteMiddlewareReaderContract {
|
|
|
34
34
|
useMap: Record<string, string>,
|
|
35
35
|
namespace: string,
|
|
36
36
|
currentClass: string,
|
|
37
|
-
|
|
38
|
-
routeDispatchedMiddleware: string[],
|
|
39
|
-
throwableCaughtMiddleware: string[],
|
|
40
|
-
sendingResponseMiddleware: string[],
|
|
41
|
-
responseSentMiddleware: string[],
|
|
37
|
+
middleware: string[],
|
|
42
38
|
): [string[], string[], string[], string[], string[]];
|
|
43
39
|
|
|
44
40
|
updateRequestStruct(
|
|
@@ -9,14 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import type { ts } from 'ts-morph';
|
|
11
11
|
|
|
12
|
-
import type { MethodDeclaration } from 'ts-morph';
|
|
13
|
-
|
|
14
12
|
import type { HttpParameterData } from '../Data/HttpParameterData.ts';
|
|
15
13
|
|
|
16
14
|
export interface HttpRouteParameterReaderContract {
|
|
17
15
|
updateParameters(
|
|
18
|
-
|
|
19
|
-
method: MethodDeclaration,
|
|
16
|
+
obj: ts.ObjectLiteralExpression,
|
|
20
17
|
useMap: Record<string, string>,
|
|
21
18
|
namespace: string,
|
|
22
19
|
currentClass: string,
|
|
@@ -14,4 +14,6 @@ export interface HttpParameterDataContract {
|
|
|
14
14
|
readonly cast: string | null;
|
|
15
15
|
readonly isOptional: boolean;
|
|
16
16
|
readonly shouldCapture: boolean;
|
|
17
|
+
/** The default value for the parameter, or null when none was declared. */
|
|
18
|
+
readonly defaultValue: string | number | boolean | null;
|
|
17
19
|
}
|
|
@@ -16,5 +16,6 @@ export class HttpParameterData implements HttpParameterDataContract {
|
|
|
16
16
|
readonly cast: string | null = null,
|
|
17
17
|
readonly isOptional: boolean = false,
|
|
18
18
|
readonly shouldCapture: boolean = true,
|
|
19
|
+
readonly defaultValue: string | number | boolean | null = null,
|
|
19
20
|
) {}
|
|
20
21
|
}
|
|
@@ -10,5 +10,9 @@
|
|
|
10
10
|
import type { ts } from 'ts-morph';
|
|
11
11
|
|
|
12
12
|
export class CliRouteAttributeResult {
|
|
13
|
-
constructor(
|
|
13
|
+
constructor(
|
|
14
|
+
readonly routes: Record<string, ts.Expression> = {},
|
|
15
|
+
/** Class short name → absolute file path for handler/help-text imports. */
|
|
16
|
+
readonly importMap: Record<string, string> = {},
|
|
17
|
+
) {}
|
|
14
18
|
}
|
|
@@ -15,5 +15,7 @@ export class HttpRouteAttributeResult {
|
|
|
15
15
|
constructor(
|
|
16
16
|
readonly routes: Record<string, ts.Expression> = {},
|
|
17
17
|
readonly routeData: Record<string, HttpRouteData> = {},
|
|
18
|
+
/** Class short name → absolute file path for handler/middleware imports. */
|
|
19
|
+
readonly importMap: Record<string, string> = {},
|
|
18
20
|
) {}
|
|
19
21
|
}
|