@valkyrjaio/sindri 26.5.1 → 26.6.1

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