@nestia/sdk 12.0.0-dev.20260601.1 → 12.0.0-dev.20260612.2

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 (209) 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/generates/CloneGenerator.js +4 -2
  48. package/lib/generates/CloneGenerator.js.map +1 -1
  49. package/lib/generates/SdkGenerator.js +50 -1
  50. package/lib/generates/SdkGenerator.js.map +1 -1
  51. package/lib/generates/SwaggerGenerator.js +18 -2
  52. package/lib/generates/SwaggerGenerator.js.map +1 -1
  53. package/lib/generates/internal/E2eFileProgrammer.js +3 -1
  54. package/lib/generates/internal/E2eFileProgrammer.js.map +1 -1
  55. package/lib/generates/internal/ImportDictionary.d.ts +1 -0
  56. package/lib/generates/internal/ImportDictionary.js +9 -4
  57. package/lib/generates/internal/ImportDictionary.js.map +1 -1
  58. package/lib/generates/internal/SdkAliasCollection.d.ts +2 -0
  59. package/lib/generates/internal/SdkAliasCollection.js +11 -2
  60. package/lib/generates/internal/SdkAliasCollection.js.map +1 -1
  61. package/lib/generates/internal/SdkDistributionComposer.d.ts +1 -0
  62. package/lib/generates/internal/SdkDistributionComposer.js +3 -0
  63. package/lib/generates/internal/SdkDistributionComposer.js.map +1 -1
  64. package/lib/generates/internal/SdkFileProgrammer.js +4 -1
  65. package/lib/generates/internal/SdkFileProgrammer.js.map +1 -1
  66. package/lib/generates/internal/SdkHttpCloneReferencer.d.ts +1 -1
  67. package/lib/generates/internal/SdkHttpCloneReferencer.js +42 -9
  68. package/lib/generates/internal/SdkHttpCloneReferencer.js.map +1 -1
  69. package/lib/generates/internal/SdkHttpFunctionProgrammer.js +3 -4
  70. package/lib/generates/internal/SdkHttpFunctionProgrammer.js.map +1 -1
  71. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js +2 -1
  72. package/lib/generates/internal/SdkHttpNamespaceProgrammer.js.map +1 -1
  73. package/lib/generates/internal/SdkHttpSimulationProgrammer.js +6 -3
  74. package/lib/generates/internal/SdkHttpSimulationProgrammer.js.map +1 -1
  75. package/lib/generates/internal/SdkMcpRouteProgrammer.d.ts +15 -0
  76. package/lib/generates/internal/SdkMcpRouteProgrammer.js +148 -0
  77. package/lib/generates/internal/SdkMcpRouteProgrammer.js.map +1 -0
  78. package/lib/generates/internal/SdkRouteDirectory.d.ts +2 -1
  79. package/lib/generates/internal/SdkRouteDirectory.js.map +1 -1
  80. package/lib/generates/internal/SdkWebSocketCloneProgrammer.d.ts +6 -0
  81. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js +283 -0
  82. package/lib/generates/internal/SdkWebSocketCloneProgrammer.js.map +1 -0
  83. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js +11 -9
  84. package/lib/generates/internal/SdkWebSocketRouteProgrammer.js.map +1 -1
  85. package/lib/generates/internal/SwaggerOperationParameterComposer.js +10 -2
  86. package/lib/generates/internal/SwaggerOperationParameterComposer.js.map +1 -1
  87. package/lib/generates/internal/SwaggerOperationResponseComposer.d.ts +1 -1
  88. package/lib/generates/internal/SwaggerOperationResponseComposer.js +6 -1
  89. package/lib/generates/internal/SwaggerOperationResponseComposer.js.map +1 -1
  90. package/lib/generates/internal/SwaggerReadonlyArrayEmender.d.ts +9 -0
  91. package/lib/generates/internal/SwaggerReadonlyArrayEmender.js +174 -0
  92. package/lib/generates/internal/SwaggerReadonlyArrayEmender.js.map +1 -0
  93. package/lib/structures/INestiaSdkInput.d.ts +9 -2
  94. package/lib/structures/IReflectController.d.ts +2 -1
  95. package/lib/structures/IReflectHttpOperationSuccess.d.ts +4 -2
  96. package/lib/structures/IReflectMcpOperation.d.ts +35 -0
  97. package/lib/structures/IReflectMcpOperation.js +3 -0
  98. package/lib/structures/IReflectMcpOperation.js.map +1 -0
  99. package/lib/structures/IReflectMcpOperationParameter.d.ts +19 -0
  100. package/lib/structures/IReflectMcpOperationParameter.js +3 -0
  101. package/lib/structures/IReflectMcpOperationParameter.js.map +1 -0
  102. package/lib/structures/ITypedApplication.d.ts +2 -1
  103. package/lib/structures/ITypedHttpRouteSuccess.d.ts +3 -1
  104. package/lib/structures/ITypedMcpRoute.d.ts +31 -0
  105. package/lib/structures/ITypedMcpRoute.js +3 -0
  106. package/lib/structures/ITypedMcpRoute.js.map +1 -0
  107. package/lib/utils/HttpResponseContentTypeUtil.d.ts +5 -0
  108. package/lib/utils/HttpResponseContentTypeUtil.js +22 -0
  109. package/lib/utils/HttpResponseContentTypeUtil.js.map +1 -0
  110. package/native/go.mod +52 -52
  111. package/native/go.sum +84 -54
  112. package/native/sdk/register.go +322 -165
  113. package/native/sdk/sdk.go +17 -17
  114. package/native/sdk/sdk_metadata_json.go +327 -327
  115. package/native/sdk/sdk_transform.go +1879 -1549
  116. package/package.json +11 -9
  117. package/src/INestiaConfig.ts +267 -267
  118. package/src/NestiaSdkApplication.ts +39 -8
  119. package/src/NestiaSwaggerComposer.ts +153 -142
  120. package/src/analyses/AccessorAnalyzer.ts +64 -67
  121. package/src/analyses/ConfigAnalyzer.ts +330 -330
  122. package/src/analyses/ImportAnalyzer.ts +92 -92
  123. package/src/analyses/PathAnalyzer.ts +130 -69
  124. package/src/analyses/ReflectControllerAnalyzer.ts +112 -105
  125. package/src/analyses/ReflectHttpOperationAnalyzer.ts +183 -183
  126. package/src/analyses/ReflectHttpOperationExceptionAnalyzer.ts +90 -90
  127. package/src/analyses/ReflectHttpOperationParameterAnalyzer.ts +350 -350
  128. package/src/analyses/ReflectHttpOperationResponseAnalyzer.ts +163 -130
  129. package/src/analyses/ReflectMcpOperationAnalyzer.ts +124 -0
  130. package/src/analyses/ReflectMetadataAnalyzer.ts +44 -44
  131. package/src/analyses/SecurityAnalyzer.ts +25 -25
  132. package/src/analyses/TypedMcpRouteAnalyzer.ts +34 -0
  133. package/src/decorators/OperationMetadata.ts +29 -29
  134. package/src/executable/internal/CommandParser.ts +15 -15
  135. package/src/executable/internal/NestiaConfigLoader.ts +451 -446
  136. package/src/executable/internal/NestiaSdkCommand.ts +124 -106
  137. package/src/executable/internal/NestiaSdkWatcher.ts +342 -0
  138. package/src/executable/sdk.ts +90 -88
  139. package/src/generates/CloneGenerator.ts +73 -66
  140. package/src/generates/E2eGenerator.ts +32 -32
  141. package/src/generates/SdkGenerator.ts +176 -118
  142. package/src/generates/SwaggerGenerator.ts +342 -310
  143. package/src/generates/internal/E2eFileProgrammer.ts +240 -233
  144. package/src/generates/internal/FilePrinter.ts +65 -65
  145. package/src/generates/internal/ImportDictionary.ts +209 -204
  146. package/src/generates/internal/SdkAliasCollection.ts +274 -261
  147. package/src/generates/internal/SdkDistributionComposer.ts +123 -116
  148. package/src/generates/internal/SdkFileProgrammer.ts +116 -112
  149. package/src/generates/internal/SdkHttpCloneProgrammer.ts +126 -126
  150. package/src/generates/internal/SdkHttpCloneReferencer.ts +131 -77
  151. package/src/generates/internal/SdkHttpFunctionProgrammer.ts +301 -301
  152. package/src/generates/internal/SdkHttpNamespaceProgrammer.ts +520 -510
  153. package/src/generates/internal/SdkHttpParameterProgrammer.ts +165 -165
  154. package/src/generates/internal/SdkHttpRouteProgrammer.ts +109 -109
  155. package/src/generates/internal/SdkHttpSimulationProgrammer.ts +331 -314
  156. package/src/generates/internal/SdkImportWizard.ts +62 -62
  157. package/src/generates/internal/SdkMcpRouteProgrammer.ts +452 -0
  158. package/src/generates/internal/SdkRouteDirectory.ts +21 -18
  159. package/src/generates/internal/SdkTypeTagProgrammer.ts +114 -114
  160. package/src/generates/internal/SdkWebSocketCloneProgrammer.ts +319 -0
  161. package/src/generates/internal/SdkWebSocketNamespaceProgrammer.ts +389 -389
  162. package/src/generates/internal/SdkWebSocketParameterProgrammer.ts +89 -89
  163. package/src/generates/internal/SdkWebSocketRouteProgrammer.ts +331 -323
  164. package/src/generates/internal/SwaggerDescriptionComposer.ts +64 -64
  165. package/src/generates/internal/SwaggerOperationComposer.ts +119 -119
  166. package/src/generates/internal/SwaggerOperationParameterComposer.ts +175 -162
  167. package/src/generates/internal/SwaggerOperationResponseComposer.ts +115 -110
  168. package/src/generates/internal/SwaggerReadonlyArrayEmender.ts +262 -0
  169. package/src/index.ts +4 -4
  170. package/src/internal/legacy.ts +492 -492
  171. package/src/module.ts +4 -4
  172. package/src/structures/INestiaProject.ts +10 -10
  173. package/src/structures/INestiaSdkInput.ts +27 -20
  174. package/src/structures/IOperationMetadata.ts +41 -41
  175. package/src/structures/IReflectController.ts +18 -15
  176. package/src/structures/IReflectHttpOperation.ts +26 -26
  177. package/src/structures/IReflectHttpOperationException.ts +18 -18
  178. package/src/structures/IReflectHttpOperationParameter.ts +79 -79
  179. package/src/structures/IReflectHttpOperationSuccess.ts +18 -21
  180. package/src/structures/IReflectImport.ts +6 -6
  181. package/src/structures/IReflectMcpOperation.ts +38 -0
  182. package/src/structures/IReflectMcpOperationParameter.ts +27 -0
  183. package/src/structures/IReflectOperationError.ts +26 -26
  184. package/src/structures/IReflectType.ts +4 -4
  185. package/src/structures/IReflectWebSocketOperation.ts +17 -17
  186. package/src/structures/ITypedApplication.ts +12 -11
  187. package/src/structures/ITypedHttpRoute.ts +41 -41
  188. package/src/structures/ITypedHttpRouteException.ts +15 -15
  189. package/src/structures/ITypedHttpRouteParameter.ts +41 -41
  190. package/src/structures/ITypedHttpRouteSuccess.ts +18 -22
  191. package/src/structures/ITypedMcpRoute.ts +33 -0
  192. package/src/structures/ITypedWebSocketRoute.ts +24 -24
  193. package/src/structures/ITypedWebSocketRouteParameter.ts +3 -3
  194. package/src/transform.ts +59 -59
  195. package/src/typings/get-function-location.d.ts +7 -7
  196. package/src/utils/ArrayUtil.ts +26 -26
  197. package/src/utils/EmittedJavaScriptPatcher.ts +88 -88
  198. package/src/utils/FileRetriever.ts +22 -22
  199. package/src/utils/HttpResponseContentTypeUtil.ts +30 -0
  200. package/src/utils/MapUtil.ts +14 -14
  201. package/src/utils/PathUtil.ts +10 -10
  202. package/src/utils/SourceFinder.ts +63 -63
  203. package/src/utils/StringUtil.ts +17 -17
  204. package/src/utils/TsConfigReader.ts +108 -108
  205. package/src/utils/TtscExecutor.ts +68 -68
  206. package/src/utils/VersioningStrategy.ts +28 -28
  207. package/src/validators/HttpHeadersValidator.ts +11 -11
  208. package/src/validators/HttpQueryValidator.ts +11 -11
  209. 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
+ }