@nestia/sdk 12.0.0-dev.20260601.1 → 12.0.0-dev.20260619.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 (259) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -93
  3. package/assets/bundle/api/HttpError.ts +1 -1
  4. package/assets/bundle/api/IConnection.ts +1 -1
  5. package/assets/bundle/api/Primitive.ts +1 -1
  6. package/assets/bundle/api/Resolved.ts +1 -1
  7. package/assets/bundle/api/index.ts +4 -4
  8. package/assets/bundle/api/module.ts +6 -6
  9. package/assets/bundle/distribute/README.md +37 -37
  10. package/assets/bundle/distribute/package.json +28 -28
  11. package/assets/bundle/distribute/tsconfig.json +109 -109
  12. package/assets/bundle/e2e/index.ts +42 -42
  13. package/assets/config/nestia.config.ts +97 -97
  14. package/lib/NestiaSdkApplication.js +29 -7
  15. package/lib/NestiaSdkApplication.js.map +1 -1
  16. package/lib/NestiaSwaggerComposer.js +21 -13
  17. package/lib/NestiaSwaggerComposer.js.map +1 -1
  18. package/lib/analyses/AccessorAnalyzer.d.ts +4 -1
  19. package/lib/analyses/AccessorAnalyzer.js.map +1 -1
  20. package/lib/analyses/ConfigAnalyzer.js +1 -1
  21. package/lib/analyses/PathAnalyzer.d.ts +18 -3
  22. package/lib/analyses/PathAnalyzer.js +32 -0
  23. package/lib/analyses/PathAnalyzer.js.map +1 -1
  24. package/lib/analyses/ReflectControllerAnalyzer.js +3 -2
  25. package/lib/analyses/ReflectControllerAnalyzer.js.map +1 -1
  26. package/lib/analyses/ReflectHttpOperationAnalyzer.d.ts +1 -1
  27. package/lib/analyses/ReflectHttpOperationAnalyzer.js +1 -1
  28. package/lib/analyses/ReflectHttpOperationAnalyzer.js.map +1 -1
  29. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.d.ts +1 -1
  30. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js +53 -20
  31. package/lib/analyses/ReflectHttpOperationResponseAnalyzer.js.map +1 -1
  32. package/lib/analyses/ReflectMcpOperationAnalyzer.d.ts +14 -0
  33. package/lib/analyses/ReflectMcpOperationAnalyzer.js +79 -0
  34. package/lib/analyses/ReflectMcpOperationAnalyzer.js.map +1 -0
  35. package/lib/analyses/TypedMcpRouteAnalyzer.d.ts +9 -0
  36. package/lib/analyses/TypedMcpRouteAnalyzer.js +31 -0
  37. package/lib/analyses/TypedMcpRouteAnalyzer.js.map +1 -0
  38. package/lib/executable/internal/NestiaConfigLoader.js +5 -1
  39. package/lib/executable/internal/NestiaConfigLoader.js.map +1 -1
  40. package/lib/executable/internal/NestiaSdkCommand.js +30 -14
  41. package/lib/executable/internal/NestiaSdkCommand.js.map +1 -1
  42. package/lib/executable/internal/NestiaSdkWatcher.d.ts +10 -0
  43. package/lib/executable/internal/NestiaSdkWatcher.js +322 -0
  44. package/lib/executable/internal/NestiaSdkWatcher.js.map +1 -0
  45. package/lib/executable/sdk.js +12 -12
  46. package/lib/executable/sdk.js.map +1 -1
  47. package/lib/factories/ExpressionFactory.d.ts +9 -0
  48. package/lib/factories/ExpressionFactory.js +16 -0
  49. package/lib/factories/ExpressionFactory.js.map +1 -0
  50. package/lib/factories/IdentifierFactory.d.ts +22 -0
  51. package/lib/factories/IdentifierFactory.js +50 -0
  52. package/lib/factories/IdentifierFactory.js.map +1 -0
  53. package/lib/factories/LiteralFactory.d.ts +9 -0
  54. package/lib/factories/LiteralFactory.js +48 -0
  55. package/lib/factories/LiteralFactory.js.map +1 -0
  56. package/lib/factories/StatementFactory.d.ts +14 -0
  57. package/lib/factories/StatementFactory.js +20 -0
  58. package/lib/factories/StatementFactory.js.map +1 -0
  59. package/lib/factories/TypeFactory.d.ts +24 -0
  60. package/lib/factories/TypeFactory.js +27 -0
  61. package/lib/factories/TypeFactory.js.map +1 -0
  62. package/lib/generates/CloneGenerator.js +6 -4
  63. package/lib/generates/CloneGenerator.js.map +1 -1
  64. package/lib/generates/SdkGenerator.js +50 -1
  65. package/lib/generates/SdkGenerator.js.map +1 -1
  66. package/lib/generates/SwaggerGenerator.js +18 -2
  67. package/lib/generates/SwaggerGenerator.js.map +1 -1
  68. package/lib/generates/internal/E2eFileProgrammer.js +32 -29
  69. package/lib/generates/internal/E2eFileProgrammer.js.map +1 -1
  70. package/lib/generates/internal/FilePrinter.d.ts +2 -2
  71. package/lib/generates/internal/FilePrinter.js +14 -25
  72. package/lib/generates/internal/FilePrinter.js.map +1 -1
  73. package/lib/generates/internal/ImportDictionary.d.ts +2 -1
  74. package/lib/generates/internal/ImportDictionary.js +16 -10
  75. package/lib/generates/internal/ImportDictionary.js.map +1 -1
  76. package/lib/generates/internal/SdkAliasCollection.d.ts +12 -10
  77. package/lib/generates/internal/SdkAliasCollection.js +29 -22
  78. package/lib/generates/internal/SdkAliasCollection.js.map +1 -1
  79. package/lib/generates/internal/SdkDistributionComposer.d.ts +1 -0
  80. package/lib/generates/internal/SdkDistributionComposer.js +3 -0
  81. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  82. package/lib/generates/internal/SdkFileProgrammer.js +6 -3
  83. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  84. package/lib/generates/internal/SdkHttpCloneProgrammer.d.ts +1 -1
  85. package/lib/generates/internal/SdkHttpCloneProgrammer.js +3 -3
  86. package/lib/generates/internal/SdkHttpCloneProgrammer.js.map +1 -1
  87. package/lib/generates/internal/SdkHttpCloneReferencer.d.ts +1 -1
  88. package/lib/generates/internal/SdkHttpCloneReferencer.js +42 -9
  89. package/lib/generates/internal/SdkHttpCloneReferencer.js.map +1 -1
  90. package/lib/generates/internal/SdkHttpFunctionProgrammer.d.ts +1 -1
  91. package/lib/generates/internal/SdkHttpFunctionProgrammer.js +47 -51
  92. package/lib/generates/internal/SdkHttpFunctionProgrammer.js.map +1 -1
  93. package/lib/generates/internal/SdkHttpNamespaceProgrammer.d.ts +1 -1
  94. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js +63 -61
  95. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js.map +1 -1
  96. package/lib/generates/internal/SdkHttpParameterProgrammer.d.ts +2 -2
  97. package/lib/generates/internal/SdkHttpParameterProgrammer.js +11 -13
  98. package/lib/generates/internal/SdkHttpParameterProgrammer.js.map +1 -1
  99. package/lib/generates/internal/SdkHttpRouteProgrammer.d.ts +1 -1
  100. package/lib/generates/internal/SdkHttpSimulationProgrammer.d.ts +1 -1
  101. package/lib/generates/internal/SdkHttpSimulationProgrammer.js +52 -47
  102. package/lib/generates/internal/SdkHttpSimulationProgrammer.js.map +1 -1
  103. package/lib/generates/internal/SdkMcpRouteProgrammer.d.ts +15 -0
  104. package/lib/generates/internal/SdkMcpRouteProgrammer.js +149 -0
  105. package/lib/generates/internal/SdkMcpRouteProgrammer.js.map +1 -0
  106. package/lib/generates/internal/SdkRouteDirectory.d.ts +2 -1
  107. package/lib/generates/internal/SdkRouteDirectory.js.map +1 -1
  108. package/lib/generates/internal/SdkTypeProgrammer.d.ts +3 -3
  109. package/lib/generates/internal/SdkTypeProgrammer.js +42 -43
  110. package/lib/generates/internal/SdkTypeProgrammer.js.map +1 -1
  111. package/lib/generates/internal/SdkTypeTagProgrammer.d.ts +2 -1
  112. package/lib/generates/internal/SdkTypeTagProgrammer.js +7 -9
  113. package/lib/generates/internal/SdkTypeTagProgrammer.js.map +1 -1
  114. package/lib/generates/internal/SdkWebSocketCloneProgrammer.d.ts +6 -0
  115. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js +283 -0
  116. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js.map +1 -0
  117. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.d.ts +2 -2
  118. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js +45 -47
  119. package/lib/generates/internal/SdkWebSocketNamespaceProgrammer.js.map +1 -1
  120. package/lib/generates/internal/SdkWebSocketParameterProgrammer.d.ts +3 -3
  121. package/lib/generates/internal/SdkWebSocketParameterProgrammer.js +9 -9
  122. package/lib/generates/internal/SdkWebSocketParameterProgrammer.js.map +1 -1
  123. package/lib/generates/internal/SdkWebSocketRouteProgrammer.d.ts +2 -2
  124. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js +37 -41
  125. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js.map +1 -1
  126. package/lib/generates/internal/SwaggerOperationParameterComposer.js +10 -2
  127. package/lib/generates/internal/SwaggerOperationParameterComposer.js.map +1 -1
  128. package/lib/generates/internal/SwaggerOperationResponseComposer.d.ts +1 -1
  129. package/lib/generates/internal/SwaggerOperationResponseComposer.js +6 -1
  130. package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
  131. package/lib/generates/internal/SwaggerReadonlyArrayEmender.d.ts +9 -0
  132. package/lib/generates/internal/SwaggerReadonlyArrayEmender.js +174 -0
  133. package/lib/generates/internal/SwaggerReadonlyArrayEmender.js.map +1 -0
  134. package/lib/structures/INestiaSdkInput.d.ts +9 -2
  135. package/lib/structures/IReflectController.d.ts +2 -1
  136. package/lib/structures/IReflectHttpOperationSuccess.d.ts +4 -2
  137. package/lib/structures/IReflectMcpOperation.d.ts +35 -0
  138. package/lib/structures/IReflectMcpOperation.js +3 -0
  139. package/lib/structures/IReflectMcpOperation.js.map +1 -0
  140. package/lib/structures/IReflectMcpOperationParameter.d.ts +19 -0
  141. package/lib/structures/IReflectMcpOperationParameter.js +3 -0
  142. package/lib/structures/IReflectMcpOperationParameter.js.map +1 -0
  143. package/lib/structures/ITypedApplication.d.ts +2 -1
  144. package/lib/structures/ITypedHttpRouteSuccess.d.ts +3 -1
  145. package/lib/structures/ITypedMcpRoute.d.ts +31 -0
  146. package/lib/structures/ITypedMcpRoute.js +3 -0
  147. package/lib/structures/ITypedMcpRoute.js.map +1 -0
  148. package/lib/transform.d.ts +5 -9
  149. package/lib/transform.js +8 -31
  150. package/lib/transform.js.map +1 -1
  151. package/lib/utils/HttpResponseContentTypeUtil.d.ts +5 -0
  152. package/lib/utils/HttpResponseContentTypeUtil.js +22 -0
  153. package/lib/utils/HttpResponseContentTypeUtil.js.map +1 -0
  154. package/native/go.mod +52 -52
  155. package/native/go.sum +84 -54
  156. package/native/sdk/register.go +322 -165
  157. package/native/sdk/sdk.go +17 -17
  158. package/native/sdk/sdk_metadata_json.go +327 -327
  159. package/native/sdk/sdk_transform.go +1879 -1549
  160. package/package.json +13 -11
  161. package/src/INestiaConfig.ts +267 -267
  162. package/src/NestiaSdkApplication.ts +39 -8
  163. package/src/NestiaSwaggerComposer.ts +153 -142
  164. package/src/analyses/AccessorAnalyzer.ts +64 -67
  165. package/src/analyses/ConfigAnalyzer.ts +330 -330
  166. package/src/analyses/ImportAnalyzer.ts +92 -92
  167. package/src/analyses/PathAnalyzer.ts +130 -69
  168. package/src/analyses/ReflectControllerAnalyzer.ts +112 -105
  169. package/src/analyses/ReflectHttpOperationAnalyzer.ts +183 -183
  170. package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +90 -90
  171. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +350 -350
  172. package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +163 -130
  173. package/src/analyses/ReflectMcpOperationAnalyzer.ts +124 -0
  174. package/src/analyses/ReflectMetadataAnalyzer.ts +44 -44
  175. package/src/analyses/SecurityAnalyzer.ts +25 -25
  176. package/src/analyses/TypedMcpRouteAnalyzer.ts +34 -0
  177. package/src/decorators/OperationMetadata.ts +29 -29
  178. package/src/executable/internal/CommandParser.ts +15 -15
  179. package/src/executable/internal/NestiaConfigLoader.ts +451 -446
  180. package/src/executable/internal/NestiaSdkCommand.ts +124 -106
  181. package/src/executable/internal/NestiaSdkWatcher.ts +342 -0
  182. package/src/executable/sdk.ts +90 -88
  183. package/src/factories/ExpressionFactory.ts +23 -0
  184. package/src/factories/IdentifierFactory.ts +84 -0
  185. package/src/factories/LiteralFactory.ts +54 -0
  186. package/src/factories/StatementFactory.ts +56 -0
  187. package/src/factories/TypeFactory.ts +27 -0
  188. package/src/generates/CloneGenerator.ts +74 -66
  189. package/src/generates/E2eGenerator.ts +32 -32
  190. package/src/generates/SdkGenerator.ts +176 -118
  191. package/src/generates/SwaggerGenerator.ts +342 -310
  192. package/src/generates/internal/E2eFileProgrammer.ts +246 -233
  193. package/src/generates/internal/FilePrinter.ts +57 -65
  194. package/src/generates/internal/ImportDictionary.ts +207 -204
  195. package/src/generates/internal/SdkAliasCollection.ts +280 -261
  196. package/src/generates/internal/SdkDistributionComposer.ts +123 -116
  197. package/src/generates/internal/SdkFileProgrammer.ts +113 -112
  198. package/src/generates/internal/SdkHttpCloneProgrammer.ts +124 -126
  199. package/src/generates/internal/SdkHttpCloneReferencer.ts +131 -77
  200. package/src/generates/internal/SdkHttpFunctionProgrammer.ts +290 -301
  201. package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +507 -510
  202. package/src/generates/internal/SdkHttpParameterProgrammer.ts +163 -165
  203. package/src/generates/internal/SdkHttpRouteProgrammer.ts +109 -109
  204. package/src/generates/internal/SdkHttpSimulationProgrammer.ts +322 -314
  205. package/src/generates/internal/SdkImportWizard.ts +62 -62
  206. package/src/generates/internal/SdkMcpRouteProgrammer.ts +447 -0
  207. package/src/generates/internal/SdkRouteDirectory.ts +21 -18
  208. package/src/generates/internal/SdkTypeProgrammer.ts +389 -382
  209. package/src/generates/internal/SdkTypeTagProgrammer.ts +110 -114
  210. package/src/generates/internal/SdkWebSocketCloneProgrammer.ts +319 -0
  211. package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +383 -389
  212. package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +91 -89
  213. package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +304 -323
  214. package/src/generates/internal/SwaggerDescriptionComposer.ts +64 -64
  215. package/src/generates/internal/SwaggerOperationComposer.ts +119 -119
  216. package/src/generates/internal/SwaggerOperationParameterComposer.ts +175 -162
  217. package/src/generates/internal/SwaggerOperationResponseComposer.ts +115 -110
  218. package/src/generates/internal/SwaggerReadonlyArrayEmender.ts +262 -0
  219. package/src/index.ts +4 -4
  220. package/src/internal/legacy.ts +492 -492
  221. package/src/module.ts +4 -4
  222. package/src/structures/INestiaProject.ts +10 -10
  223. package/src/structures/INestiaSdkInput.ts +27 -20
  224. package/src/structures/IOperationMetadata.ts +41 -41
  225. package/src/structures/IReflectController.ts +18 -15
  226. package/src/structures/IReflectHttpOperation.ts +26 -26
  227. package/src/structures/IReflectHttpOperationException.ts +18 -18
  228. package/src/structures/IReflectHttpOperationParameter.ts +79 -79
  229. package/src/structures/IReflectHttpOperationSuccess.ts +18 -21
  230. package/src/structures/IReflectImport.ts +6 -6
  231. package/src/structures/IReflectMcpOperation.ts +38 -0
  232. package/src/structures/IReflectMcpOperationParameter.ts +27 -0
  233. package/src/structures/IReflectOperationError.ts +26 -26
  234. package/src/structures/IReflectType.ts +4 -4
  235. package/src/structures/IReflectWebSocketOperation.ts +17 -17
  236. package/src/structures/ITypedApplication.ts +12 -11
  237. package/src/structures/ITypedHttpRoute.ts +41 -41
  238. package/src/structures/ITypedHttpRouteException.ts +15 -15
  239. package/src/structures/ITypedHttpRouteParameter.ts +41 -41
  240. package/src/structures/ITypedHttpRouteSuccess.ts +18 -22
  241. package/src/structures/ITypedMcpRoute.ts +33 -0
  242. package/src/structures/ITypedWebSocketRoute.ts +24 -24
  243. package/src/structures/ITypedWebSocketRouteParameter.ts +3 -3
  244. package/src/transform.ts +28 -59
  245. package/src/typings/get-function-location.d.ts +7 -7
  246. package/src/utils/ArrayUtil.ts +26 -26
  247. package/src/utils/EmittedJavaScriptPatcher.ts +88 -88
  248. package/src/utils/FileRetriever.ts +22 -22
  249. package/src/utils/HttpResponseContentTypeUtil.ts +30 -0
  250. package/src/utils/MapUtil.ts +14 -14
  251. package/src/utils/PathUtil.ts +10 -10
  252. package/src/utils/SourceFinder.ts +63 -63
  253. package/src/utils/StringUtil.ts +17 -17
  254. package/src/utils/TsConfigReader.ts +108 -108
  255. package/src/utils/TtscExecutor.ts +68 -68
  256. package/src/utils/VersioningStrategy.ts +28 -28
  257. package/src/validators/HttpHeadersValidator.ts +11 -11
  258. package/src/validators/HttpQueryValidator.ts +11 -11
  259. package/src/validators/TextPlainValidator.ts +17 -17
@@ -1,446 +1,451 @@
1
- import { doNotThrowTransformError } from "@nestia/core";
2
- import fs from "fs";
3
- import path from "path";
4
- import { pathToFileURL } from "url";
5
-
6
- import { INestiaConfig } from "../../INestiaConfig";
7
- import { EmittedJavaScriptPatcher } from "../../utils/EmittedJavaScriptPatcher";
8
- import { TsConfigReader } from "../../utils/TsConfigReader";
9
- import { TtscExecutor } from "../../utils/TtscExecutor";
10
-
11
- export namespace NestiaConfigLoader {
12
- export interface ICompilerOptions {
13
- raw: {
14
- compilerOptions?: Record<string, any>;
15
- };
16
- }
17
-
18
- export const compilerOptions = async (
19
- project: string,
20
- ): Promise<ICompilerOptions> => {
21
- const configFileName = findConfigFile(process.cwd(), project);
22
- if (!configFileName) throw new Error(`unable to find "${project}" file.`);
23
- const tsconfig = await TsConfigReader.read(configFileName);
24
- return {
25
- raw: {
26
- compilerOptions:
27
- typeof tsconfig?.compilerOptions === "object"
28
- ? (tsconfig.compilerOptions as Record<string, any>)
29
- : {},
30
- },
31
- };
32
- };
33
-
34
- export const configurations = async (
35
- file: string,
36
- compilerOptions: Record<string, any>,
37
- ): Promise<INestiaConfig[]> => {
38
- if (fs.existsSync(path.resolve(file)) === false)
39
- throw new Error(`Unable to find "${file}" file.`);
40
-
41
- doNotThrowTransformError(false);
42
-
43
- if (compilerOptions.plugins !== undefined)
44
- assertPlugins(file, compilerOptions.plugins);
45
-
46
- const configFile: string = await materializeConfiguration({
47
- file,
48
- compilerOptions,
49
- });
50
- const loaded: unknown = await loadMaterializedModule(configFile);
51
- const instance: INestiaConfig | INestiaConfig[] = extractConfiguration(
52
- file,
53
- loaded,
54
- );
55
- const configurations: INestiaConfig[] = Array.isArray(instance)
56
- ? instance
57
- : [instance];
58
-
59
- return assertConfigurations(file, configurations);
60
- };
61
-
62
- const MATERIALIZED_ROOTS: Set<string> = new Set();
63
- let CLEANUP_REGISTERED: boolean = false;
64
-
65
- const materializeConfiguration = async (props: {
66
- file: string;
67
- compilerOptions: Record<string, any>;
68
- }): Promise<string> => {
69
- const configFile: string = path.resolve(props.file);
70
- const project: string = process.env.NESTIA_PROJECT ?? "tsconfig.json";
71
- const projectFile: string | undefined = findConfigFile(
72
- process.cwd(),
73
- project,
74
- );
75
- if (projectFile === undefined)
76
- throw new Error(`unable to find "${project}" file.`);
77
-
78
- const projectRoot: string = path.dirname(path.resolve(projectFile));
79
- const wrapperRoot: string = fs.mkdtempSync(
80
- path.join(ensureMaterializedRoot(projectRoot), "tsconfig-"),
81
- );
82
- const outputRoot: string = fs.mkdtempSync(
83
- path.join(ensureMaterializedRoot(projectRoot), "run-"),
84
- );
85
- const wrapperFile: string = path.join(wrapperRoot, "tsconfig.json");
86
- const wrapperConfig = {
87
- extends: projectFile,
88
- compilerOptions: {
89
- noEmit: false,
90
- noUnusedLocals: false,
91
- noUnusedParameters: false,
92
- ...nodeAmbientCompilerOptions(projectRoot, props.compilerOptions),
93
- outDir: outputRoot,
94
- plugins: materializePlugins(props.compilerOptions.plugins),
95
- rootDir: projectRoot,
96
- },
97
- include: [configFile],
98
- exclude: [path.join(projectRoot, "src", "test", "**", "*")],
99
- };
100
- fs.writeFileSync(
101
- wrapperFile,
102
- JSON.stringify(wrapperConfig, null, 2),
103
- "utf8",
104
- );
105
-
106
- try {
107
- TtscExecutor.run({
108
- cwd: projectRoot,
109
- env: sdkTransformEnv(),
110
- project: wrapperFile,
111
- });
112
- } catch (error) {
113
- const stderr: string = readChildOutput(error, "stderr");
114
- const stdout: string = readChildOutput(error, "stdout");
115
- const detail: string = stderr || stdout;
116
- const cause: Error =
117
- error instanceof Error ? error : new Error(String(error));
118
- const status: number | string | undefined =
119
- (cause as { status?: number }).status ??
120
- (cause as NodeJS.ErrnoException).code;
121
- throw new Error(
122
- detail
123
- ? `failed to compile "${props.file}" through ttsc:\n${detail}`
124
- : `failed to compile "${props.file}" through ttsc (exit code ${status ?? "unknown"}). Run \`npx ttsc -p ${projectFile}\` to see the underlying diagnostics.`,
125
- { cause },
126
- );
127
- } finally {
128
- fs.rmSync(wrapperRoot, { force: true, recursive: true });
129
- }
130
- await EmittedJavaScriptPatcher.importMetaUrl(outputRoot);
131
-
132
- const configKey: string = emittedJavaScriptKey(projectRoot, configFile);
133
- const next: string = path.join(outputRoot, configKey);
134
- if (fs.existsSync(next) === false)
135
- throw new Error(
136
- `failed to materialize "${props.file}" through ttsc native transform.`,
137
- );
138
- return next;
139
- };
140
-
141
- const ensureMaterializedRoot = (projectRoot: string): string => {
142
- const root: string = path.join(
143
- projectRoot,
144
- "node_modules",
145
- ".nestia",
146
- "config-loader",
147
- );
148
- fs.mkdirSync(root, { recursive: true });
149
- MATERIALIZED_ROOTS.add(root);
150
- if (CLEANUP_REGISTERED === false) {
151
- CLEANUP_REGISTERED = true;
152
- const sweep = (): void => {
153
- for (const location of MATERIALIZED_ROOTS)
154
- fs.rmSync(location, { force: true, recursive: true });
155
- };
156
- process.once("exit", sweep);
157
- // process.once("exit", …) does not fire on SIGINT/SIGTERM. Without
158
- // these handlers a Ctrl-C during codegen leaves `run-*` and
159
- // `tsconfig-*` mkdtempSync directories behind under
160
- // node_modules/.nestia/config-loader/ until a subsequent clean exit.
161
- // The module-level CLEANUP_REGISTERED flag above guards against
162
- // re-entrancy within this module; we deliberately do NOT gate on
163
- // `process.listenerCount(signal) > 0` because the parallel sweep in
164
- // `ConfigAnalyzer.ensureRuntimeCleanup` (or any user-app SIGINT
165
- // handler) could register first, and that gate would skip our
166
- // registration leaving MATERIALIZED_ROOTS unswept on Ctrl-C.
167
- // Windows note: `process.kill(pid, "SIGINT")` calls TerminateProcess
168
- // rather than re-raising; whichever module registers FIRST runs its
169
- // sweep, the second is skipped, RUNTIME/MATERIALIZED cleanup is
170
- // best-effort on Windows. SIGHUP is a no-op for most common code
171
- // paths on Windows (Node fires it on console-close and exits within
172
- // seconds).
173
- const onSignal = (signal: NodeJS.Signals): void => {
174
- sweep();
175
- process.kill(process.pid, signal);
176
- };
177
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
178
- process.once(signal, onSignal);
179
- }
180
- }
181
- return root;
182
- };
183
-
184
- const emittedJavaScriptKey = (projectRoot: string, file: string): string => {
185
- const relative: string = path.relative(projectRoot, file);
186
- const extension: string = path.extname(relative).toLowerCase();
187
- const emitted: string =
188
- extension === ".mts" ? ".mjs" : extension === ".cts" ? ".cjs" : ".js";
189
- return path
190
- .join(
191
- path.dirname(relative),
192
- `${path.basename(relative, extension)}${emitted}`,
193
- )
194
- .split(path.sep)
195
- .join(path.posix.sep);
196
- };
197
-
198
- const nodeAmbientCompilerOptions = (
199
- projectRoot: string,
200
- compilerOptions: Record<string, any>,
201
- ): { typeRoots?: string[]; types: string[] } => {
202
- const typeRoots: string[] = uniqueStrings([
203
- ...asStringArray(compilerOptions.typeRoots),
204
- ...resolveNodeTypeRoots(projectRoot),
205
- ]);
206
- const types: string[] = uniqueStrings([
207
- "node",
208
- ...asStringArray(compilerOptions.types).filter((value) => value !== "*"),
209
- ]);
210
- return {
211
- ...(typeRoots.length !== 0 ? { typeRoots } : {}),
212
- types,
213
- };
214
- };
215
-
216
- const resolveNodeTypeRoots = (projectRoot: string): string[] => {
217
- const roots: string[] = [];
218
- for (const base of [projectRoot, process.cwd(), __dirname])
219
- try {
220
- const location: string = require.resolve("@types/node/package.json", {
221
- paths: [base],
222
- });
223
- roots.push(path.dirname(path.dirname(location)));
224
- } catch {
225
- continue;
226
- }
227
- return roots;
228
- };
229
-
230
- const asStringArray = (input: unknown): string[] =>
231
- Array.isArray(input)
232
- ? input.filter((elem): elem is string => typeof elem === "string")
233
- : [];
234
-
235
- const uniqueStrings = (input: string[]): string[] => [...new Set(input)];
236
-
237
- type MaterializePlugin = Record<string, unknown> & { transform?: unknown };
238
-
239
- const sdkTransformEnv = (): NodeJS.ProcessEnv =>
240
- process.env.NESTIA_SDK_TRANSFORM === undefined
241
- ? { NESTIA_SDK_TRANSFORM: "1" }
242
- : {};
243
-
244
- const materializePlugins = (input: unknown): MaterializePlugin[] => {
245
- const plugins: MaterializePlugin[] = Array.isArray(input)
246
- ? input
247
- .filter((p) => typeof p === "object" && p !== null)
248
- .map((p) => ({ ...(p as MaterializePlugin) }))
249
- : [];
250
- const typia: MaterializePlugin | undefined = plugins.find((p) =>
251
- isTransform(p, "typia"),
252
- );
253
- const core: MaterializePlugin | undefined = plugins.find((p) =>
254
- isTransform(p, "@nestia/core"),
255
- );
256
- return [
257
- {
258
- ...(typia ?? {}),
259
- transform: "typia/lib/transform",
260
- enabled: false,
261
- },
262
- normalizePlugin({
263
- ...(core ?? {}),
264
- transform: "@nestia/core/native/transform.cjs",
265
- }),
266
- ];
267
- };
268
-
269
- const normalizePlugin = (plugin: MaterializePlugin): MaterializePlugin => {
270
- const output: MaterializePlugin = { ...plugin };
271
- if (output.enabled === false) delete output.enabled;
272
- return output;
273
- };
274
-
275
- const isTransform = (plugin: MaterializePlugin, name: string): boolean =>
276
- typeof plugin.transform === "string" && plugin.transform.includes(name);
277
-
278
- const findConfigFile = (cwd: string, project: string): string | undefined => {
279
- const candidate: string = path.isAbsolute(project)
280
- ? project
281
- : path.resolve(cwd, project);
282
- if (fs.existsSync(candidate)) return candidate;
283
- if (path.isAbsolute(project) || project.includes(path.sep))
284
- return undefined;
285
-
286
- let current: string = path.resolve(cwd);
287
- while (true) {
288
- const next: string = path.join(current, project);
289
- if (fs.existsSync(next)) return next;
290
- const parent: string = path.dirname(current);
291
- if (parent === current) return undefined;
292
- current = parent;
293
- }
294
- };
295
-
296
- const extractConfiguration = (
297
- file: string,
298
- loaded: unknown,
299
- ): INestiaConfig | INestiaConfig[] => {
300
- const candidates: unknown[] = [];
301
- const collect = (value: unknown): void => {
302
- if (isObject(value)) {
303
- candidates.push(value.default);
304
- candidates.push(value.NESTIA_CONFIG);
305
- if (isObject(value.default)) {
306
- candidates.push(value.default.default);
307
- candidates.push(value.default.NESTIA_CONFIG);
308
- }
309
- }
310
- candidates.push(value);
311
- };
312
- collect(loaded);
313
- const matched: unknown = candidates.find(
314
- (value) =>
315
- Array.isArray(value) ||
316
- (isObject(value) && Object.hasOwn(value, "input")),
317
- );
318
- if (matched === undefined)
319
- throw new Error(
320
- `invalid "${file}" data: configuration must be exported.`,
321
- );
322
- return matched as INestiaConfig | INestiaConfig[];
323
- };
324
-
325
- const loadMaterializedModule = async (file: string): Promise<unknown> => {
326
- if (file.endsWith(".mjs")) {
327
- const dynamicImport = new Function(
328
- "specifier",
329
- "return import(specifier)",
330
- ) as (specifier: string) => Promise<unknown>;
331
- return dynamicImport(pathToFileURL(file).href);
332
- }
333
- return require(file);
334
- };
335
-
336
- const assertPlugins = (
337
- file: string,
338
- input: unknown,
339
- ): Array<Record<string, any>> => {
340
- if (
341
- Array.isArray(input) &&
342
- input.every((elem) => typeof elem === "object" && elem !== null)
343
- )
344
- return input as Array<Record<string, any>>;
345
- throw new Error(
346
- `invalid "${file}" data: compilerOptions.plugins must be an array.`,
347
- );
348
- };
349
-
350
- const assertConfigurations = (
351
- file: string,
352
- input: unknown[],
353
- ): INestiaConfig[] => {
354
- input.forEach((config, index) => assertConfig(file, config, index));
355
- return input as INestiaConfig[];
356
- };
357
-
358
- const assertConfig = (file: string, input: unknown, index: number): void => {
359
- if (isObject(input) === false)
360
- throw new Error(
361
- `invalid "${file}" data: configuration #${index} must be an object.`,
362
- );
363
- const config: INestiaConfig = input as unknown as INestiaConfig;
364
- if (isInput(config.input) === false)
365
- throw new Error(
366
- `invalid "${file}" data: configuration #${index}.input is invalid.`,
367
- );
368
- for (const [key, value] of [
369
- ["output", config.output],
370
- ["distribute", config.distribute],
371
- ["e2e", config.e2e],
372
- ] as const)
373
- if (value !== undefined && typeof value !== "string")
374
- throw new Error(
375
- `invalid "${file}" data: configuration #${index}.${key} must be a string.`,
376
- );
377
- for (const [key, value] of [
378
- ["keyword", config.keyword],
379
- ["simulate", config.simulate],
380
- ["propagate", config.propagate],
381
- ["clone", config.clone],
382
- ["primitive", config.primitive],
383
- ["assert", config.assert],
384
- ["json", config.json],
385
- ] as const)
386
- if (value !== undefined && typeof value !== "boolean")
387
- throw new Error(
388
- `invalid "${file}" data: configuration #${index}.${key} must be a boolean.`,
389
- );
390
- if (config.swagger !== undefined && isSwagger(config.swagger) === false)
391
- throw new Error(
392
- `invalid "${file}" data: configuration #${index}.swagger is invalid.`,
393
- );
394
- };
395
-
396
- const isInput = (input: unknown): input is INestiaConfig["input"] => {
397
- if (
398
- typeof input === "string" ||
399
- typeof input === "function" ||
400
- isStringArray(input)
401
- )
402
- return true;
403
- if (isObject(input) === false) return false;
404
- return (
405
- isStringArray(input.include) &&
406
- (input.exclude === undefined || isStringArray(input.exclude))
407
- );
408
- };
409
-
410
- const isSwagger = (input: unknown): input is INestiaConfig.ISwaggerConfig => {
411
- if (isObject(input) === false) return false;
412
- return (
413
- typeof input.output === "string" &&
414
- (input.openapi === undefined ||
415
- ["2.0", "3.0", "3.1", "3.2"].includes(input.openapi as string)) &&
416
- (input.beautify === undefined ||
417
- typeof input.beautify === "boolean" ||
418
- typeof input.beautify === "number") &&
419
- (input.additional === undefined ||
420
- typeof input.additional === "boolean") &&
421
- (input.decompose === undefined || typeof input.decompose === "boolean") &&
422
- (input.operationId === undefined ||
423
- typeof input.operationId === "function")
424
- );
425
- };
426
-
427
- const isObject = (input: unknown): input is Record<string, unknown> =>
428
- typeof input === "object" &&
429
- input !== null &&
430
- Array.isArray(input) === false;
431
-
432
- const isStringArray = (input: unknown): input is string[] =>
433
- Array.isArray(input) && input.every((elem) => typeof elem === "string");
434
-
435
- const readChildOutput = (
436
- error: unknown,
437
- key: "stderr" | "stdout",
438
- ): string => {
439
- if (!error || typeof error !== "object" || !(key in error)) return "";
440
- const value = (error as Record<string, unknown>)[key];
441
- if (value === null || value === undefined) return "";
442
- if (typeof value === "string") return value.trim();
443
- if (Buffer.isBuffer(value)) return value.toString("utf8").trim();
444
- return "";
445
- };
446
- }
1
+ import { doNotThrowTransformError } from "@nestia/core";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { pathToFileURL } from "url";
5
+
6
+ import { INestiaConfig } from "../../INestiaConfig";
7
+ import { EmittedJavaScriptPatcher } from "../../utils/EmittedJavaScriptPatcher";
8
+ import { TsConfigReader } from "../../utils/TsConfigReader";
9
+ import { TtscExecutor } from "../../utils/TtscExecutor";
10
+
11
+ export namespace NestiaConfigLoader {
12
+ export interface ICompilerOptions {
13
+ raw: {
14
+ compilerOptions?: Record<string, any>;
15
+ };
16
+ }
17
+
18
+ export const compilerOptions = async (
19
+ project: string,
20
+ ): Promise<ICompilerOptions> => {
21
+ const configFileName = findConfigFile(process.cwd(), project);
22
+ if (!configFileName) throw new Error(`unable to find "${project}" file.`);
23
+ const tsconfig = await TsConfigReader.read(configFileName);
24
+ return {
25
+ raw: {
26
+ compilerOptions:
27
+ typeof tsconfig?.compilerOptions === "object"
28
+ ? (tsconfig.compilerOptions as Record<string, any>)
29
+ : {},
30
+ },
31
+ };
32
+ };
33
+
34
+ export const configurations = async (
35
+ file: string,
36
+ compilerOptions: Record<string, any>,
37
+ ): Promise<INestiaConfig[]> => {
38
+ if (fs.existsSync(path.resolve(file)) === false)
39
+ throw new Error(`Unable to find "${file}" file.`);
40
+
41
+ doNotThrowTransformError(false);
42
+
43
+ if (compilerOptions.plugins !== undefined)
44
+ assertPlugins(file, compilerOptions.plugins);
45
+
46
+ const configFile: string = await materializeConfiguration({
47
+ file,
48
+ compilerOptions,
49
+ });
50
+ const loaded: unknown = await loadMaterializedModule(configFile);
51
+ const instance: INestiaConfig | INestiaConfig[] = extractConfiguration(
52
+ file,
53
+ loaded,
54
+ );
55
+ const configurations: INestiaConfig[] = Array.isArray(instance)
56
+ ? instance
57
+ : [instance];
58
+
59
+ return assertConfigurations(file, configurations);
60
+ };
61
+
62
+ const MATERIALIZED_ROOTS: Set<string> = new Set();
63
+ let CLEANUP_REGISTERED: boolean = false;
64
+
65
+ const materializeConfiguration = async (props: {
66
+ file: string;
67
+ compilerOptions: Record<string, any>;
68
+ }): Promise<string> => {
69
+ const configFile: string = path.resolve(props.file);
70
+ const project: string = process.env.NESTIA_PROJECT ?? "tsconfig.json";
71
+ const projectFile: string | undefined = findConfigFile(
72
+ process.cwd(),
73
+ project,
74
+ );
75
+ if (projectFile === undefined)
76
+ throw new Error(`unable to find "${project}" file.`);
77
+
78
+ const projectRoot: string = path.dirname(path.resolve(projectFile));
79
+ const wrapperRoot: string = fs.mkdtempSync(
80
+ path.join(ensureMaterializedRoot(projectRoot), "tsconfig-"),
81
+ );
82
+ const outputRoot: string = fs.mkdtempSync(
83
+ path.join(ensureMaterializedRoot(projectRoot), "run-"),
84
+ );
85
+ const wrapperFile: string = path.join(wrapperRoot, "tsconfig.json");
86
+ const wrapperConfig = {
87
+ extends: projectFile,
88
+ compilerOptions: {
89
+ noEmit: false,
90
+ noUnusedLocals: false,
91
+ noUnusedParameters: false,
92
+ ...nodeAmbientCompilerOptions(projectRoot, props.compilerOptions),
93
+ // The wrapper compiles the config file only to require() its JS; .d.ts
94
+ // is never read, and tsgo's declaration emitter can nil-panic on a
95
+ // config that calls into Nest. Force it off regardless of the project.
96
+ declaration: false,
97
+ declarationMap: false,
98
+ outDir: outputRoot,
99
+ plugins: materializePlugins(props.compilerOptions.plugins),
100
+ rootDir: projectRoot,
101
+ },
102
+ include: [configFile],
103
+ exclude: [path.join(projectRoot, "src", "test", "**", "*")],
104
+ };
105
+ fs.writeFileSync(
106
+ wrapperFile,
107
+ JSON.stringify(wrapperConfig, null, 2),
108
+ "utf8",
109
+ );
110
+
111
+ try {
112
+ TtscExecutor.run({
113
+ cwd: projectRoot,
114
+ env: sdkTransformEnv(),
115
+ project: wrapperFile,
116
+ });
117
+ } catch (error) {
118
+ const stderr: string = readChildOutput(error, "stderr");
119
+ const stdout: string = readChildOutput(error, "stdout");
120
+ const detail: string = stderr || stdout;
121
+ const cause: Error =
122
+ error instanceof Error ? error : new Error(String(error));
123
+ const status: number | string | undefined =
124
+ (cause as { status?: number }).status ??
125
+ (cause as NodeJS.ErrnoException).code;
126
+ throw new Error(
127
+ detail
128
+ ? `failed to compile "${props.file}" through ttsc:\n${detail}`
129
+ : `failed to compile "${props.file}" through ttsc (exit code ${status ?? "unknown"}). Run \`npx ttsc -p ${projectFile}\` to see the underlying diagnostics.`,
130
+ { cause },
131
+ );
132
+ } finally {
133
+ fs.rmSync(wrapperRoot, { force: true, recursive: true });
134
+ }
135
+ await EmittedJavaScriptPatcher.importMetaUrl(outputRoot);
136
+
137
+ const configKey: string = emittedJavaScriptKey(projectRoot, configFile);
138
+ const next: string = path.join(outputRoot, configKey);
139
+ if (fs.existsSync(next) === false)
140
+ throw new Error(
141
+ `failed to materialize "${props.file}" through ttsc native transform.`,
142
+ );
143
+ return next;
144
+ };
145
+
146
+ const ensureMaterializedRoot = (projectRoot: string): string => {
147
+ const root: string = path.join(
148
+ projectRoot,
149
+ "node_modules",
150
+ ".nestia",
151
+ "config-loader",
152
+ );
153
+ fs.mkdirSync(root, { recursive: true });
154
+ MATERIALIZED_ROOTS.add(root);
155
+ if (CLEANUP_REGISTERED === false) {
156
+ CLEANUP_REGISTERED = true;
157
+ const sweep = (): void => {
158
+ for (const location of MATERIALIZED_ROOTS)
159
+ fs.rmSync(location, { force: true, recursive: true });
160
+ };
161
+ process.once("exit", sweep);
162
+ // process.once("exit", …) does not fire on SIGINT/SIGTERM. Without
163
+ // these handlers a Ctrl-C during codegen leaves `run-*` and
164
+ // `tsconfig-*` mkdtempSync directories behind under
165
+ // node_modules/.nestia/config-loader/ until a subsequent clean exit.
166
+ // The module-level CLEANUP_REGISTERED flag above guards against
167
+ // re-entrancy within this module; we deliberately do NOT gate on
168
+ // `process.listenerCount(signal) > 0` because the parallel sweep in
169
+ // `ConfigAnalyzer.ensureRuntimeCleanup` (or any user-app SIGINT
170
+ // handler) could register first, and that gate would skip our
171
+ // registration leaving MATERIALIZED_ROOTS unswept on Ctrl-C.
172
+ // Windows note: `process.kill(pid, "SIGINT")` calls TerminateProcess
173
+ // rather than re-raising; whichever module registers FIRST runs its
174
+ // sweep, the second is skipped, RUNTIME/MATERIALIZED cleanup is
175
+ // best-effort on Windows. SIGHUP is a no-op for most common code
176
+ // paths on Windows (Node fires it on console-close and exits within
177
+ // seconds).
178
+ const onSignal = (signal: NodeJS.Signals): void => {
179
+ sweep();
180
+ process.kill(process.pid, signal);
181
+ };
182
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
183
+ process.once(signal, onSignal);
184
+ }
185
+ }
186
+ return root;
187
+ };
188
+
189
+ const emittedJavaScriptKey = (projectRoot: string, file: string): string => {
190
+ const relative: string = path.relative(projectRoot, file);
191
+ const extension: string = path.extname(relative).toLowerCase();
192
+ const emitted: string =
193
+ extension === ".mts" ? ".mjs" : extension === ".cts" ? ".cjs" : ".js";
194
+ return path
195
+ .join(
196
+ path.dirname(relative),
197
+ `${path.basename(relative, extension)}${emitted}`,
198
+ )
199
+ .split(path.sep)
200
+ .join(path.posix.sep);
201
+ };
202
+
203
+ const nodeAmbientCompilerOptions = (
204
+ projectRoot: string,
205
+ compilerOptions: Record<string, any>,
206
+ ): { typeRoots?: string[]; types: string[] } => {
207
+ const typeRoots: string[] = uniqueStrings([
208
+ ...asStringArray(compilerOptions.typeRoots),
209
+ ...resolveNodeTypeRoots(projectRoot),
210
+ ]);
211
+ const types: string[] = uniqueStrings([
212
+ "node",
213
+ ...asStringArray(compilerOptions.types).filter((value) => value !== "*"),
214
+ ]);
215
+ return {
216
+ ...(typeRoots.length !== 0 ? { typeRoots } : {}),
217
+ types,
218
+ };
219
+ };
220
+
221
+ const resolveNodeTypeRoots = (projectRoot: string): string[] => {
222
+ const roots: string[] = [];
223
+ for (const base of [projectRoot, process.cwd(), __dirname])
224
+ try {
225
+ const location: string = require.resolve("@types/node/package.json", {
226
+ paths: [base],
227
+ });
228
+ roots.push(path.dirname(path.dirname(location)));
229
+ } catch {
230
+ continue;
231
+ }
232
+ return roots;
233
+ };
234
+
235
+ const asStringArray = (input: unknown): string[] =>
236
+ Array.isArray(input)
237
+ ? input.filter((elem): elem is string => typeof elem === "string")
238
+ : [];
239
+
240
+ const uniqueStrings = (input: string[]): string[] => [...new Set(input)];
241
+
242
+ type MaterializePlugin = Record<string, unknown> & { transform?: unknown };
243
+
244
+ const sdkTransformEnv = (): NodeJS.ProcessEnv =>
245
+ process.env.NESTIA_SDK_TRANSFORM === undefined
246
+ ? { NESTIA_SDK_TRANSFORM: "1" }
247
+ : {};
248
+
249
+ const materializePlugins = (input: unknown): MaterializePlugin[] => {
250
+ const plugins: MaterializePlugin[] = Array.isArray(input)
251
+ ? input
252
+ .filter((p) => typeof p === "object" && p !== null)
253
+ .map((p) => ({ ...(p as MaterializePlugin) }))
254
+ : [];
255
+ const typia: MaterializePlugin | undefined = plugins.find((p) =>
256
+ isTransform(p, "typia"),
257
+ );
258
+ const core: MaterializePlugin | undefined = plugins.find((p) =>
259
+ isTransform(p, "@nestia/core"),
260
+ );
261
+ return [
262
+ {
263
+ ...(typia ?? {}),
264
+ transform: "typia/lib/transform",
265
+ enabled: false,
266
+ },
267
+ normalizePlugin({
268
+ ...(core ?? {}),
269
+ transform: "@nestia/core/native/transform.cjs",
270
+ }),
271
+ ];
272
+ };
273
+
274
+ const normalizePlugin = (plugin: MaterializePlugin): MaterializePlugin => {
275
+ const output: MaterializePlugin = { ...plugin };
276
+ if (output.enabled === false) delete output.enabled;
277
+ return output;
278
+ };
279
+
280
+ const isTransform = (plugin: MaterializePlugin, name: string): boolean =>
281
+ typeof plugin.transform === "string" && plugin.transform.includes(name);
282
+
283
+ const findConfigFile = (cwd: string, project: string): string | undefined => {
284
+ const candidate: string = path.isAbsolute(project)
285
+ ? project
286
+ : path.resolve(cwd, project);
287
+ if (fs.existsSync(candidate)) return candidate;
288
+ if (path.isAbsolute(project) || project.includes(path.sep))
289
+ return undefined;
290
+
291
+ let current: string = path.resolve(cwd);
292
+ while (true) {
293
+ const next: string = path.join(current, project);
294
+ if (fs.existsSync(next)) return next;
295
+ const parent: string = path.dirname(current);
296
+ if (parent === current) return undefined;
297
+ current = parent;
298
+ }
299
+ };
300
+
301
+ const extractConfiguration = (
302
+ file: string,
303
+ loaded: unknown,
304
+ ): INestiaConfig | INestiaConfig[] => {
305
+ const candidates: unknown[] = [];
306
+ const collect = (value: unknown): void => {
307
+ if (isObject(value)) {
308
+ candidates.push(value.default);
309
+ candidates.push(value.NESTIA_CONFIG);
310
+ if (isObject(value.default)) {
311
+ candidates.push(value.default.default);
312
+ candidates.push(value.default.NESTIA_CONFIG);
313
+ }
314
+ }
315
+ candidates.push(value);
316
+ };
317
+ collect(loaded);
318
+ const matched: unknown = candidates.find(
319
+ (value) =>
320
+ Array.isArray(value) ||
321
+ (isObject(value) && Object.hasOwn(value, "input")),
322
+ );
323
+ if (matched === undefined)
324
+ throw new Error(
325
+ `invalid "${file}" data: configuration must be exported.`,
326
+ );
327
+ return matched as INestiaConfig | INestiaConfig[];
328
+ };
329
+
330
+ const loadMaterializedModule = async (file: string): Promise<unknown> => {
331
+ if (file.endsWith(".mjs")) {
332
+ const dynamicImport = new Function(
333
+ "specifier",
334
+ "return import(specifier)",
335
+ ) as (specifier: string) => Promise<unknown>;
336
+ return dynamicImport(pathToFileURL(file).href);
337
+ }
338
+ return require(file);
339
+ };
340
+
341
+ const assertPlugins = (
342
+ file: string,
343
+ input: unknown,
344
+ ): Array<Record<string, any>> => {
345
+ if (
346
+ Array.isArray(input) &&
347
+ input.every((elem) => typeof elem === "object" && elem !== null)
348
+ )
349
+ return input as Array<Record<string, any>>;
350
+ throw new Error(
351
+ `invalid "${file}" data: compilerOptions.plugins must be an array.`,
352
+ );
353
+ };
354
+
355
+ const assertConfigurations = (
356
+ file: string,
357
+ input: unknown[],
358
+ ): INestiaConfig[] => {
359
+ input.forEach((config, index) => assertConfig(file, config, index));
360
+ return input as INestiaConfig[];
361
+ };
362
+
363
+ const assertConfig = (file: string, input: unknown, index: number): void => {
364
+ if (isObject(input) === false)
365
+ throw new Error(
366
+ `invalid "${file}" data: configuration #${index} must be an object.`,
367
+ );
368
+ const config: INestiaConfig = input as unknown as INestiaConfig;
369
+ if (isInput(config.input) === false)
370
+ throw new Error(
371
+ `invalid "${file}" data: configuration #${index}.input is invalid.`,
372
+ );
373
+ for (const [key, value] of [
374
+ ["output", config.output],
375
+ ["distribute", config.distribute],
376
+ ["e2e", config.e2e],
377
+ ] as const)
378
+ if (value !== undefined && typeof value !== "string")
379
+ throw new Error(
380
+ `invalid "${file}" data: configuration #${index}.${key} must be a string.`,
381
+ );
382
+ for (const [key, value] of [
383
+ ["keyword", config.keyword],
384
+ ["simulate", config.simulate],
385
+ ["propagate", config.propagate],
386
+ ["clone", config.clone],
387
+ ["primitive", config.primitive],
388
+ ["assert", config.assert],
389
+ ["json", config.json],
390
+ ] as const)
391
+ if (value !== undefined && typeof value !== "boolean")
392
+ throw new Error(
393
+ `invalid "${file}" data: configuration #${index}.${key} must be a boolean.`,
394
+ );
395
+ if (config.swagger !== undefined && isSwagger(config.swagger) === false)
396
+ throw new Error(
397
+ `invalid "${file}" data: configuration #${index}.swagger is invalid.`,
398
+ );
399
+ };
400
+
401
+ const isInput = (input: unknown): input is INestiaConfig["input"] => {
402
+ if (
403
+ typeof input === "string" ||
404
+ typeof input === "function" ||
405
+ isStringArray(input)
406
+ )
407
+ return true;
408
+ if (isObject(input) === false) return false;
409
+ return (
410
+ isStringArray(input.include) &&
411
+ (input.exclude === undefined || isStringArray(input.exclude))
412
+ );
413
+ };
414
+
415
+ const isSwagger = (input: unknown): input is INestiaConfig.ISwaggerConfig => {
416
+ if (isObject(input) === false) return false;
417
+ return (
418
+ typeof input.output === "string" &&
419
+ (input.openapi === undefined ||
420
+ ["2.0", "3.0", "3.1", "3.2"].includes(input.openapi as string)) &&
421
+ (input.beautify === undefined ||
422
+ typeof input.beautify === "boolean" ||
423
+ typeof input.beautify === "number") &&
424
+ (input.additional === undefined ||
425
+ typeof input.additional === "boolean") &&
426
+ (input.decompose === undefined || typeof input.decompose === "boolean") &&
427
+ (input.operationId === undefined ||
428
+ typeof input.operationId === "function")
429
+ );
430
+ };
431
+
432
+ const isObject = (input: unknown): input is Record<string, unknown> =>
433
+ typeof input === "object" &&
434
+ input !== null &&
435
+ Array.isArray(input) === false;
436
+
437
+ const isStringArray = (input: unknown): input is string[] =>
438
+ Array.isArray(input) && input.every((elem) => typeof elem === "string");
439
+
440
+ const readChildOutput = (
441
+ error: unknown,
442
+ key: "stderr" | "stdout",
443
+ ): string => {
444
+ if (!error || typeof error !== "object" || !(key in error)) return "";
445
+ const value = (error as Record<string, unknown>)[key];
446
+ if (value === null || value === undefined) return "";
447
+ if (typeof value === "string") return value.trim();
448
+ if (Buffer.isBuffer(value)) return value.toString("utf8").trim();
449
+ return "";
450
+ };
451
+ }