@nestia/core 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 (116) hide show
  1. package/LICENSE +21 -21
  2. package/MIGRATION.md +169 -169
  3. package/README.md +93 -93
  4. package/lib/adaptors/McpAdaptor.d.ts +75 -0
  5. package/lib/adaptors/McpAdaptor.js +257 -0
  6. package/lib/adaptors/McpAdaptor.js.map +1 -0
  7. package/lib/adaptors/WebSocketAdaptor.js +4 -4
  8. package/lib/adaptors/WebSocketAdaptor.js.map +1 -1
  9. package/lib/decorators/McpRoute.d.ts +69 -0
  10. package/lib/decorators/McpRoute.js +58 -0
  11. package/lib/decorators/McpRoute.js.map +1 -0
  12. package/lib/decorators/TypedParam.js +4 -4
  13. package/lib/decorators/TypedParam.js.map +1 -1
  14. package/lib/decorators/TypedRoute.js +1 -1
  15. package/lib/decorators/TypedRoute.js.map +1 -1
  16. package/lib/decorators/internal/IMcpRouteReflect.d.ts +2 -0
  17. package/lib/decorators/internal/IMcpRouteReflect.js +3 -0
  18. package/lib/decorators/internal/IMcpRouteReflect.js.map +1 -0
  19. package/lib/decorators/internal/get_path_and_querify.js +4 -4
  20. package/lib/decorators/internal/get_path_and_querify.js.map +1 -1
  21. package/lib/decorators/internal/get_path_and_stringify.js +4 -4
  22. package/lib/decorators/internal/get_path_and_stringify.js.map +1 -1
  23. package/lib/decorators/internal/load_controller.js +34 -65
  24. package/lib/decorators/internal/load_controller.js.map +1 -1
  25. package/lib/decorators/internal/validate_request_body.js +4 -4
  26. package/lib/decorators/internal/validate_request_body.js.map +1 -1
  27. package/lib/decorators/internal/validate_request_form_data.js +4 -4
  28. package/lib/decorators/internal/validate_request_form_data.js.map +1 -1
  29. package/lib/decorators/internal/validate_request_headers.js +4 -4
  30. package/lib/decorators/internal/validate_request_headers.js.map +1 -1
  31. package/lib/decorators/internal/validate_request_query.js +4 -4
  32. package/lib/decorators/internal/validate_request_query.js.map +1 -1
  33. package/lib/module.d.ts +2 -0
  34. package/lib/module.js +2 -0
  35. package/lib/module.js.map +1 -1
  36. package/native/cmd/ttsc-nestia/main.go +11 -11
  37. package/native/go.mod +32 -32
  38. package/native/go.sum +54 -54
  39. package/native/plugin/plan.go +102 -102
  40. package/native/transform/ast.go +32 -32
  41. package/native/transform/build.go +380 -444
  42. package/native/transform/cleanup.go +408 -408
  43. package/native/transform/contributor.go +97 -68
  44. package/native/transform/core_querify.go +231 -227
  45. package/native/transform/core_transform.go +1996 -1713
  46. package/native/transform/core_websocket.go +115 -115
  47. package/native/transform/exports.go +13 -13
  48. package/native/transform/mcp_transform.go +414 -0
  49. package/native/transform/node_transform.go +357 -0
  50. package/native/transform/path_rewrite.go +285 -285
  51. package/native/transform/printer.go +244 -244
  52. package/native/transform/rewrite.go +668 -662
  53. package/native/transform/run.go +73 -73
  54. package/native/transform/transform.go +336 -403
  55. package/native/transform/typia_fast.go +352 -326
  56. package/native/transform/typia_replacement.go +24 -24
  57. package/native/transform.cjs +43 -43
  58. package/package.json +15 -8
  59. package/src/adaptors/McpAdaptor.ts +276 -0
  60. package/src/adaptors/WebSocketAdaptor.ts +429 -429
  61. package/src/decorators/DynamicModule.ts +44 -44
  62. package/src/decorators/EncryptedBody.ts +97 -97
  63. package/src/decorators/EncryptedController.ts +40 -40
  64. package/src/decorators/EncryptedModule.ts +98 -98
  65. package/src/decorators/EncryptedRoute.ts +213 -213
  66. package/src/decorators/HumanRoute.ts +21 -21
  67. package/src/decorators/McpRoute.ts +154 -0
  68. package/src/decorators/NoTransformConfigurationError.ts +40 -40
  69. package/src/decorators/PlainBody.ts +76 -76
  70. package/src/decorators/SwaggerCustomizer.ts +97 -97
  71. package/src/decorators/SwaggerExample.ts +180 -180
  72. package/src/decorators/TypedBody.ts +57 -57
  73. package/src/decorators/TypedException.ts +147 -147
  74. package/src/decorators/TypedFormData.ts +187 -187
  75. package/src/decorators/TypedHeaders.ts +66 -66
  76. package/src/decorators/TypedParam.ts +77 -77
  77. package/src/decorators/TypedQuery.ts +234 -234
  78. package/src/decorators/TypedRoute.ts +198 -196
  79. package/src/decorators/WebSocketRoute.ts +242 -242
  80. package/src/decorators/doNotThrowTransformError.ts +5 -5
  81. package/src/decorators/internal/EncryptedConstant.ts +2 -2
  82. package/src/decorators/internal/IMcpRouteReflect.ts +40 -0
  83. package/src/decorators/internal/IWebSocketRouteReflect.ts +23 -23
  84. package/src/decorators/internal/get_path_and_querify.ts +94 -94
  85. package/src/decorators/internal/get_path_and_stringify.ts +110 -110
  86. package/src/decorators/internal/get_text_body.ts +16 -16
  87. package/src/decorators/internal/headers_to_object.ts +11 -11
  88. package/src/decorators/internal/is_request_body_undefined.ts +12 -12
  89. package/src/decorators/internal/load_controller.ts +91 -76
  90. package/src/decorators/internal/route_error.ts +43 -43
  91. package/src/decorators/internal/validate_request_body.ts +64 -64
  92. package/src/decorators/internal/validate_request_form_data.ts +67 -67
  93. package/src/decorators/internal/validate_request_headers.ts +76 -76
  94. package/src/decorators/internal/validate_request_query.ts +83 -83
  95. package/src/index.ts +5 -5
  96. package/src/module.ts +25 -23
  97. package/src/options/IRequestBodyValidator.ts +20 -20
  98. package/src/options/IRequestFormDataProps.ts +27 -27
  99. package/src/options/IRequestHeadersValidator.ts +22 -22
  100. package/src/options/IRequestQueryValidator.ts +20 -20
  101. package/src/options/IResponseBodyQuerifier.ts +25 -25
  102. package/src/options/IResponseBodyStringifier.ts +30 -30
  103. package/src/transform.ts +101 -101
  104. package/src/typings/Creator.ts +3 -3
  105. package/src/typings/get-function-location.d.ts +7 -7
  106. package/src/utils/ArrayUtil.ts +7 -7
  107. package/src/utils/ExceptionManager.ts +115 -115
  108. package/src/utils/Singleton.ts +16 -16
  109. package/src/utils/SourceFinder.ts +54 -54
  110. package/src/utils/VersioningStrategy.ts +27 -27
  111. package/native/transform/cleanup_test.go +0 -76
  112. package/native/transform/commonjs_import_alias_test.go +0 -49
  113. package/native/transform/core_dispatch_test.go +0 -127
  114. package/native/transform/path_rewrite_test.go +0 -243
  115. package/native/transform/rewrite_test.go +0 -118
  116. package/native/transform/rewrite_unique_base_test.go +0 -48
@@ -1,444 +1,380 @@
1
- package transform
2
-
3
- import (
4
- "encoding/json"
5
- "flag"
6
- "fmt"
7
- "io"
8
- "os"
9
- "path/filepath"
10
- "regexp"
11
- "time"
12
-
13
- shimast "github.com/microsoft/typescript-go/shim/ast"
14
- shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
15
- shimscanner "github.com/microsoft/typescript-go/shim/scanner"
16
- "github.com/samchon/nestia/packages/core/native/plugin"
17
- "github.com/samchon/ttsc/packages/ttsc/driver"
18
- typiaadapter "github.com/samchon/typia/packages/typia/native/adapter"
19
- )
20
-
21
- func runBuild(args []string) int {
22
- profile := os.Getenv("TTSC_NESTIA_PROFILE") != ""
23
- var totalStarted time.Time
24
- if profile {
25
- totalStarted = time.Now()
26
- }
27
- fs := flag.NewFlagSet("build", flag.ContinueOnError)
28
- fs.SetOutput(stderr)
29
- tsconfigPath := fs.String("tsconfig", "tsconfig.json", "path to tsconfig.json")
30
- cwdOverride := fs.String("cwd", "", "override the working directory")
31
- quiet := fs.Bool("quiet", true, "suppress the per-call diagnostic summary")
32
- verbose := fs.Bool("verbose", false, "print the per-call diagnostic summary")
33
- emit := fs.Bool("emit", false, "force emitted .js files")
34
- noEmit := fs.Bool("noEmit", false, "force analysis-only run with no file writes")
35
- outDir := fs.String("outDir", "", "override compilerOptions.outDir")
36
- manifestPath := fs.String("manifest", "", "write emitted file list as JSON")
37
- pluginsJSON := fs.String("plugins-json", "", "ordered ttsc plugin payload")
38
- if err := fs.Parse(args); err != nil {
39
- return 2
40
- }
41
- if *emit && *noEmit {
42
- fmt.Fprintln(stderr, "ttsc-nestia build: --emit and --noEmit are mutually exclusive")
43
- return 2
44
- }
45
- if *verbose {
46
- *quiet = false
47
- }
48
- plan, err := plugin.ParsePlan(*pluginsJSON)
49
- if err != nil {
50
- fmt.Fprintf(stderr, "ttsc-nestia build: %v\n", err)
51
- return 2
52
- }
53
-
54
- cwd, ok := resolveCWD("ttsc-nestia build", *cwdOverride)
55
- if !ok {
56
- return 2
57
- }
58
- var started time.Time
59
- if profile {
60
- started = time.Now()
61
- }
62
- prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
63
- ForceEmit: *emit,
64
- ForceNoEmit: *noEmit,
65
- OutDir: *outDir,
66
- })
67
- profileBuildStep(profile, "load-program", started)
68
- if err != nil {
69
- fmt.Fprintf(stderr, "ttsc-nestia build: %v\n", err)
70
- return 2
71
- }
72
- if len(diags) > 0 {
73
- driver.WritePrettyDiagnostics(stderr, diags, cwd)
74
- return 2
75
- }
76
- defer prog.Close()
77
- if profile {
78
- started = time.Now()
79
- }
80
- if diags := prog.Diagnostics(); len(diags) > 0 {
81
- profileBuildStep(profile, "diagnostics", started)
82
- driver.WritePrettyDiagnostics(stderr, diags, cwd)
83
- return 2
84
- }
85
- profileBuildStep(profile, "diagnostics", started)
86
-
87
- shouldEmit := !prog.ParsedConfig.ParsedConfig.CompilerOptions.NoEmit.IsTrue()
88
- if !*quiet {
89
- fmt.Fprintf(
90
- stdout,
91
- "// ttsc-nestia build: tsconfig=%s cwd=%s core=%v sdk=%v typia=%v emit=%v\n",
92
- *tsconfigPath,
93
- cwd,
94
- plan.Core,
95
- plan.SDK,
96
- plan.Typia,
97
- shouldEmit,
98
- )
99
- }
100
- if !shouldEmit {
101
- return 0
102
- }
103
-
104
- rewrites := newNativeRewriteSet()
105
- if profile {
106
- started = time.Now()
107
- }
108
- sites, recognized, transformDiags := collectTypiaRewrites(
109
- prog,
110
- cwd,
111
- shouldEmit,
112
- *quiet,
113
- "",
114
- rewrites,
115
- readTypiaPluginOptions(cwd, *tsconfigPath),
116
- )
117
- profileBuildStepCount(profile, "typia-rewrites", started, rewrites.Len())
118
- if len(transformDiags) > 0 {
119
- WriteTypiaTransformDiagnostics(stderr, transformDiags, cwd)
120
- return 3
121
- }
122
- beforeCore := rewrites.Len()
123
- if profile {
124
- started = time.Now()
125
- }
126
- coreDiags := collectNestiaCoreBuildRewrites(prog, plan, rewrites)
127
- profileBuildStepCount(profile, "core-rewrites", started, rewrites.Len()-beforeCore)
128
- if len(coreDiags) > 0 {
129
- WriteTypiaTransformDiagnostics(stderr, coreDiags, cwd)
130
- return 3
131
- }
132
- if profile {
133
- started = time.Now()
134
- }
135
- contributorRewriters, contributorDiags := collectContributorBuildOutputRewriters(prog, plan)
136
- contributorCount := 0
137
- for _, rewriter := range contributorRewriters {
138
- if rewriter.Len != nil {
139
- contributorCount += rewriter.Len()
140
- }
141
- }
142
- profileBuildStepCount(profile, "contributor-rewrites", started, contributorCount)
143
- if len(contributorDiags) > 0 {
144
- WriteTypiaTransformDiagnostics(stderr, contributorDiags, cwd)
145
- return 3
146
- }
147
- if profile {
148
- started = time.Now()
149
- }
150
- newPathsRewriter(prog).applyAll(prog.SourceFiles())
151
- profileBuildStep(profile, "paths-rewrite", started)
152
-
153
- cursors := map[string]int{}
154
- var nativePatchElapsed time.Duration
155
- var cleanupElapsed time.Duration
156
- var writeElapsed time.Duration
157
- writeFile := shimcompiler.WriteFile(func(fileName, text string, data *shimcompiler.WriteFileData) error {
158
- _ = data
159
- var patchStarted time.Time
160
- if profile {
161
- patchStarted = time.Now()
162
- }
163
- patched, err := rewrites.Apply(fileName, text, cursors)
164
- if profile {
165
- nativePatchElapsed += time.Since(patchStarted)
166
- }
167
- if err != nil {
168
- return err
169
- }
170
- for _, rewriter := range contributorRewriters {
171
- if rewriter.Apply == nil {
172
- continue
173
- }
174
- patched, err = rewriter.Apply(fileName, patched)
175
- if err != nil {
176
- return err
177
- }
178
- }
179
- if profile {
180
- patchStarted = time.Now()
181
- }
182
- patched = cleanupTransformedTextWithRuntimeAliases(patched, rewrites.RuntimeAliasesForOutput(fileName))
183
- if profile {
184
- cleanupElapsed += time.Since(patchStarted)
185
- patchStarted = time.Now()
186
- defer func() {
187
- writeElapsed += time.Since(patchStarted)
188
- }()
189
- }
190
- return driver.DefaultWriteFile(fileName, patched)
191
- })
192
- if profile {
193
- started = time.Now()
194
- }
195
- res, eDiags, err := prog.EmitAllRaw(writeFile)
196
- profileBuildStep(profile, "emit-total", started)
197
- profileBuildDuration(profile, "emit-native-patch", nativePatchElapsed)
198
- profileBuildDuration(profile, "emit-cleanup", cleanupElapsed)
199
- profileBuildDuration(profile, "emit-write", writeElapsed)
200
- if err != nil {
201
- fmt.Fprintf(stderr, "ttsc-nestia build: emit failed: %v\n", err)
202
- return 3
203
- }
204
- emitHasError := false
205
- for _, d := range eDiags {
206
- fmt.Fprintln(stderr, " -", d.String())
207
- if d.IsError() {
208
- emitHasError = true
209
- }
210
- }
211
- if emitHasError {
212
- return 3
213
- }
214
- if *manifestPath != "" {
215
- data, err := json.Marshal(res.EmittedFiles)
216
- if err != nil {
217
- fmt.Fprintf(stderr, "ttsc-nestia build: manifest marshal failed: %v\n", err)
218
- return 3
219
- }
220
- if err := os.MkdirAll(filepath.Dir(*manifestPath), 0o755); err != nil {
221
- fmt.Fprintf(stderr, "ttsc-nestia build: manifest mkdir failed: %v\n", err)
222
- return 3
223
- }
224
- if err := os.WriteFile(*manifestPath, data, 0o644); err != nil {
225
- fmt.Fprintf(stderr, "ttsc-nestia build: manifest write failed: %v\n", err)
226
- return 3
227
- }
228
- }
229
- if !*quiet {
230
- fmt.Fprintf(stdout, "// ttsc-nestia build: typia recognized=%d total=%d rewrites=%d\n", recognized, sites, rewrites.Len())
231
- }
232
- profileBuildStep(profile, "total", totalStarted)
233
- return 0
234
- }
235
-
236
- func runCheck(args []string) int {
237
- fs := flag.NewFlagSet("check", flag.ContinueOnError)
238
- fs.SetOutput(stderr)
239
- tsconfigPath := fs.String("tsconfig", "tsconfig.json", "path to tsconfig.json")
240
- cwdOverride := fs.String("cwd", "", "override the working directory")
241
- pluginsJSON := fs.String("plugins-json", "", "ordered ttsc plugin payload")
242
- if err := fs.Parse(args); err != nil {
243
- return 2
244
- }
245
- if _, err := plugin.ParsePlan(*pluginsJSON); err != nil {
246
- fmt.Fprintf(stderr, "ttsc-nestia check: %v\n", err)
247
- return 2
248
- }
249
- cwd, ok := resolveCWD("ttsc-nestia check", *cwdOverride)
250
- if !ok {
251
- return 2
252
- }
253
- prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
254
- ForceNoEmit: true,
255
- })
256
- if err != nil {
257
- fmt.Fprintf(stderr, "ttsc-nestia check: %v\n", err)
258
- return 2
259
- }
260
- if len(diags) > 0 {
261
- driver.WritePrettyDiagnostics(stderr, diags, cwd)
262
- return 2
263
- }
264
- defer prog.Close()
265
- if diags := prog.Diagnostics(); len(diags) > 0 {
266
- driver.WritePrettyDiagnostics(stderr, diags, cwd)
267
- return 2
268
- }
269
- return 0
270
- }
271
-
272
- type Diagnostic struct {
273
- File string
274
- Line int
275
- Column int
276
- Code string
277
- Message string
278
- }
279
-
280
- func (d Diagnostic) String(cwd string) string {
281
- file := d.File
282
- if rel, err := filepath.Rel(cwd, file); err == nil {
283
- file = rel
284
- }
285
- if d.Line > 0 {
286
- return fmt.Sprintf("%s:%d:%d - error TS(%s): %s", file, d.Line, d.Column, d.Code, d.Message)
287
- }
288
- return fmt.Sprintf("%s - error TS(%s): %s", file, d.Code, d.Message)
289
- }
290
-
291
- func WriteTypiaTransformDiagnostics(out io.Writer, diagnostics []Diagnostic, cwd string) {
292
- for _, diag := range diagnostics {
293
- fmt.Fprintln(out, diag.String(cwd))
294
- }
295
- }
296
-
297
- func profileBuildStep(enabled bool, name string, started time.Time) {
298
- if enabled {
299
- profileBuildDuration(enabled, name, time.Since(started))
300
- }
301
- }
302
-
303
- func profileBuildStepCount(enabled bool, name string, started time.Time, count int) {
304
- if enabled {
305
- fmt.Fprintf(stderr, "ttsc-nestia profile: %s=%s count=%d\n", name, time.Since(started), count)
306
- }
307
- }
308
-
309
- func profileBuildDuration(enabled bool, name string, elapsed time.Duration) {
310
- if enabled {
311
- fmt.Fprintf(stderr, "ttsc-nestia profile: %s=%s\n", name, elapsed)
312
- }
313
- }
314
-
315
- func NewDiagnostic(site typiaadapter.CallSite, message string) Diagnostic {
316
- line, column := 0, 0
317
- if site.File != nil && site.Call != nil {
318
- pos := site.Call.AsNode().Pos()
319
- if pos >= 0 {
320
- l, c := shimscanner.GetECMALineAndByteOffsetOfPosition(site.File, pos)
321
- line, column = l+1, c+1
322
- }
323
- }
324
- return Diagnostic{
325
- File: site.FilePath,
326
- Line: line,
327
- Column: column,
328
- Code: "typia." + site.Module + "." + site.Method,
329
- Message: message,
330
- }
331
- }
332
-
333
- func collectTypiaRewrites(
334
- prog *driver.Program,
335
- cwd string,
336
- emit bool,
337
- quiet bool,
338
- onlyFile string,
339
- rewrites *nativeRewriteSet,
340
- pluginOptions typiaadapter.PluginOptions,
341
- ) (int, int, []Diagnostic) {
342
- sites := collectNestiaTypiaCallSites(prog.SourceFiles(), prog.Checker)
343
- recognized := 0
344
- diagnostics := []Diagnostic{}
345
- for _, site := range sites {
346
- if onlyFile != "" && filepath.ToSlash(site.FilePath) != filepath.ToSlash(onlyFile) {
347
- continue
348
- }
349
- rel := site.FilePath
350
- if abs, err := filepath.Rel(cwd, rel); err == nil {
351
- rel = abs
352
- }
353
- if reason := typiaadapter.UnsupportedReason(site); reason != "" {
354
- diagnostics = append(diagnostics, NewDiagnostic(site, reason))
355
- continue
356
- }
357
- expr, handled, err := typiaadapter.EmitCallWithOptions(prog, site, pluginOptions)
358
- if !handled {
359
- diagnostics = append(diagnostics, NewDiagnostic(site, "method not covered"))
360
- continue
361
- }
362
- if err != nil {
363
- diagnostics = append(diagnostics, NewDiagnostic(site, err.Error()))
364
- continue
365
- }
366
- expr = parenthesizeTypiaReplacement(site, expr)
367
- rewrites.Add(nativeRewrite{
368
- FilePath: site.FilePath,
369
- RootName: site.RootName,
370
- Namespaces: site.Namespaces,
371
- Method: site.Method,
372
- Replacement: expr,
373
- ConsumeParens: true,
374
- SourceStart: typiaBuildRewriteSortKey(site),
375
- })
376
- if !emit && !quiet {
377
- fmt.Fprintf(stdout, "%s: typia.%s<T> -> %s\n", rel, site.Method, expr)
378
- }
379
- recognized++
380
- }
381
- return len(sites), recognized, diagnostics
382
- }
383
-
384
- func typiaBuildRewriteSortKey(site typiaadapter.CallSite) int {
385
- node := site.Call.AsNode()
386
- if node == nil {
387
- return 0
388
- }
389
- insideDecorator := false
390
- classEnd := 0
391
- for current := node.Parent; current != nil; current = current.Parent {
392
- if current.Kind == NestiaCoreKindDecorator {
393
- insideDecorator = true
394
- }
395
- if current.Kind == shimast.KindClassDeclaration || current.Kind == shimast.KindClassExpression {
396
- classEnd = current.End()
397
- break
398
- }
399
- }
400
- if insideDecorator == false {
401
- return node.Pos()
402
- }
403
- if classEnd != 0 {
404
- return classEnd + node.Pos()
405
- }
406
- return node.Pos() + 1_000_000_000
407
- }
408
-
409
- func readTypiaPluginOptions(cwd, tsconfigPath string) typiaadapter.PluginOptions {
410
- path := tsconfigPath
411
- if !filepath.IsAbs(path) {
412
- path = filepath.Join(cwd, path)
413
- }
414
- data, err := os.ReadFile(path)
415
- if err != nil {
416
- return typiaadapter.PluginOptions{}
417
- }
418
- text := string(data)
419
- return typiaadapter.PluginOptions{
420
- Functional: typiaOptionFunctionalPattern.MatchString(text),
421
- Numeric: typiaOptionNumericPattern.MatchString(text),
422
- Finite: typiaOptionFinitePattern.MatchString(text),
423
- Undefined: typiaOptionUndefinedPattern.MatchString(text),
424
- }
425
- }
426
-
427
- var (
428
- typiaOptionFunctionalPattern = regexp.MustCompile(`(?s)"functional"\s*:\s*true`)
429
- typiaOptionNumericPattern = regexp.MustCompile(`(?s)"numeric"\s*:\s*true`)
430
- typiaOptionFinitePattern = regexp.MustCompile(`(?s)"finite"\s*:\s*true`)
431
- typiaOptionUndefinedPattern = regexp.MustCompile(`(?s)"undefined"\s*:\s*true`)
432
- )
433
-
434
- func resolveCWD(label string, cwdOverride string) (string, bool) {
435
- if cwdOverride != "" {
436
- return cwdOverride, true
437
- }
438
- cwd, err := os.Getwd()
439
- if err != nil {
440
- fmt.Fprintf(stderr, "%s: cwd: %v\n", label, err)
441
- return "", false
442
- }
443
- return cwd, true
444
- }
1
+ package transform
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "flag"
7
+ "fmt"
8
+ "io"
9
+ "os"
10
+ "path/filepath"
11
+ "regexp"
12
+ "time"
13
+
14
+ shimast "github.com/microsoft/typescript-go/shim/ast"
15
+ shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
16
+ shimscanner "github.com/microsoft/typescript-go/shim/scanner"
17
+ "github.com/samchon/nestia/packages/core/native/plugin"
18
+ "github.com/samchon/ttsc/packages/ttsc/driver"
19
+ typiaadapter "github.com/samchon/typia/packages/typia/native/adapter"
20
+ )
21
+
22
+ func runBuild(args []string) int {
23
+ profile := os.Getenv("TTSC_NESTIA_PROFILE") != ""
24
+ var totalStarted time.Time
25
+ if profile {
26
+ totalStarted = time.Now()
27
+ }
28
+ fs := flag.NewFlagSet("build", flag.ContinueOnError)
29
+ fs.SetOutput(stderr)
30
+ tsconfigPath := fs.String("tsconfig", "tsconfig.json", "path to tsconfig.json")
31
+ cwdOverride := fs.String("cwd", "", "override the working directory")
32
+ quiet := fs.Bool("quiet", true, "suppress the per-call diagnostic summary")
33
+ verbose := fs.Bool("verbose", false, "print the per-call diagnostic summary")
34
+ emit := fs.Bool("emit", false, "force emitted .js files")
35
+ noEmit := fs.Bool("noEmit", false, "force analysis-only run with no file writes")
36
+ outDir := fs.String("outDir", "", "override compilerOptions.outDir")
37
+ manifestPath := fs.String("manifest", "", "write emitted file list as JSON")
38
+ pluginsJSON := fs.String("plugins-json", "", "ordered ttsc plugin payload")
39
+ if err := fs.Parse(args); err != nil {
40
+ return 2
41
+ }
42
+ if *emit && *noEmit {
43
+ fmt.Fprintln(stderr, "ttsc-nestia build: --emit and --noEmit are mutually exclusive")
44
+ return 2
45
+ }
46
+ if *verbose {
47
+ *quiet = false
48
+ }
49
+ plan, err := plugin.ParsePlan(*pluginsJSON)
50
+ if err != nil {
51
+ fmt.Fprintf(stderr, "ttsc-nestia build: %v\n", err)
52
+ return 2
53
+ }
54
+
55
+ cwd, ok := resolveCWD("ttsc-nestia build", *cwdOverride)
56
+ if !ok {
57
+ return 2
58
+ }
59
+ var started time.Time
60
+ if profile {
61
+ started = time.Now()
62
+ }
63
+ prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
64
+ ForceEmit: *emit,
65
+ ForceNoEmit: *noEmit,
66
+ OutDir: *outDir,
67
+ })
68
+ profileBuildStep(profile, "load-program", started)
69
+ if err != nil {
70
+ fmt.Fprintf(stderr, "ttsc-nestia build: %v\n", err)
71
+ return 2
72
+ }
73
+ if len(diags) > 0 {
74
+ driver.WritePrettyDiagnostics(stderr, diags, cwd)
75
+ return 2
76
+ }
77
+ defer prog.Close()
78
+ if profile {
79
+ started = time.Now()
80
+ }
81
+ if diags := prog.Diagnostics(); len(diags) > 0 {
82
+ profileBuildStep(profile, "diagnostics", started)
83
+ driver.WritePrettyDiagnostics(stderr, diags, cwd)
84
+ return 2
85
+ }
86
+ profileBuildStep(profile, "diagnostics", started)
87
+
88
+ shouldEmit := !prog.ParsedConfig.ParsedConfig.CompilerOptions.NoEmit.IsTrue()
89
+ if !*quiet {
90
+ fmt.Fprintf(
91
+ stdout,
92
+ "// ttsc-nestia build: tsconfig=%s cwd=%s core=%v sdk=%v typia=%v emit=%v\n",
93
+ *tsconfigPath,
94
+ cwd,
95
+ plan.Core,
96
+ plan.SDK,
97
+ plan.Typia,
98
+ shouldEmit,
99
+ )
100
+ }
101
+ if !shouldEmit {
102
+ return 0
103
+ }
104
+
105
+ // AST-integration emit: typia's per-file transformer and @nestia/core's own
106
+ // per-file transformer both run inside tsgo's emit pipeline (sharing the
107
+ // EmitContext), so they return AST and tsgo's module-transform aliases the
108
+ // namespace imports they inject. No text-splice RewriteSet, no cleanup pass.
109
+ transformDiags := []Diagnostic{}
110
+ addDiagnostic := func(diag Diagnostic) {
111
+ transformDiags = append(transformDiags, diag)
112
+ }
113
+ if profile {
114
+ started = time.Now()
115
+ }
116
+ newPathsRewriter(prog).applyAll(prog.SourceFiles())
117
+ profileBuildStep(profile, "paths-rewrite", started)
118
+
119
+ typiaTransform := nestiaTypiaNodeTransform(prog, readTypiaPluginOptions(cwd, *tsconfigPath), addDiagnostic)
120
+ coreTransform := nestiaCoreNodeTransform(prog, plan, addDiagnostic)
121
+
122
+ // Statically linked contributors (e.g. the @nestia/sdk OperationMetadata
123
+ // pass) run their per-file emit transformer in the same EmitContext after
124
+ // typia and core, mirroring the `transform` subcommand. Without this the
125
+ // build/emit path silently drops a contributor that opted in through its own
126
+ // flag (NESTIA_SDK_TRANSFORM) rather than the ttsc linked-plugin registry, so
127
+ // its metadata never reaches the emitted JavaScript.
128
+ contributorTransforms, contributorDiags := collectContributorEmitTransforms(prog, plan)
129
+ if len(contributorDiags) > 0 {
130
+ WriteTypiaTransformDiagnostics(stderr, contributorDiags, cwd)
131
+ return 3
132
+ }
133
+ transforms := append([]driver.PluginTransform{typiaTransform, coreTransform}, contributorTransforms...)
134
+
135
+ emitted := []string{}
136
+ writeFile := shimcompiler.WriteFile(func(fileName, text string, data *shimcompiler.WriteFileData) error {
137
+ _ = data
138
+ emitted = append(emitted, fileName)
139
+ return driver.DefaultWriteFile(fileName, text)
140
+ })
141
+
142
+ // Declaration emit: ttsc delegates the whole emit of a transform-plugin
143
+ // package to this host, but EmitWithPluginTransformers writes only .js. A
144
+ // library package (e.g. @nestia/core itself) ships .d.ts, so emit the
145
+ // declarations here with tsgo's standard declaration emitter. The typia /
146
+ // core runtime transforms never change the public type surface, so the
147
+ // declarations are taken from the pristine program — done before the JS
148
+ // transform runs so it reads the un-mutated AST.
149
+ if prog.ParsedConfig.ParsedConfig.CompilerOptions.Declaration.IsTrue() {
150
+ if profile {
151
+ started = time.Now()
152
+ }
153
+ emitDeclarations(prog, writeFile)
154
+ profileBuildStep(profile, "declaration-emit", started)
155
+ }
156
+
157
+ if profile {
158
+ started = time.Now()
159
+ }
160
+ eDiags, err := prog.EmitWithPluginTransformers(transforms, writeFile)
161
+ profileBuildStep(profile, "emit-total", started)
162
+ if err != nil {
163
+ fmt.Fprintf(stderr, "ttsc-nestia build: emit failed: %v\n", err)
164
+ return 3
165
+ }
166
+ if len(transformDiags) > 0 {
167
+ WriteTypiaTransformDiagnostics(stderr, transformDiags, cwd)
168
+ return 3
169
+ }
170
+ emitHasError := false
171
+ for _, d := range eDiags {
172
+ fmt.Fprintln(stderr, " -", d.String())
173
+ if d.IsError() {
174
+ emitHasError = true
175
+ }
176
+ }
177
+ if emitHasError {
178
+ return 3
179
+ }
180
+ if *manifestPath != "" {
181
+ data, err := json.Marshal(emitted)
182
+ if err != nil {
183
+ fmt.Fprintf(stderr, "ttsc-nestia build: manifest marshal failed: %v\n", err)
184
+ return 3
185
+ }
186
+ if err := os.MkdirAll(filepath.Dir(*manifestPath), 0o755); err != nil {
187
+ fmt.Fprintf(stderr, "ttsc-nestia build: manifest mkdir failed: %v\n", err)
188
+ return 3
189
+ }
190
+ if err := os.WriteFile(*manifestPath, data, 0o644); err != nil {
191
+ fmt.Fprintf(stderr, "ttsc-nestia build: manifest write failed: %v\n", err)
192
+ return 3
193
+ }
194
+ }
195
+ if !*quiet {
196
+ fmt.Fprintf(stdout, "// ttsc-nestia build: emitted=%d files\n", len(emitted))
197
+ }
198
+ profileBuildStep(profile, "total", totalStarted)
199
+ return 0
200
+ }
201
+
202
+ // emitDeclarations runs tsgo's standard declaration emitter for the program.
203
+ // tsgo's MarkLinkedReferences pass can nil-panic on some cross-module reference
204
+ // shapes (e.g. a nestia.config.ts that calls NestFactory.create, compiled by the
205
+ // SDK config loader only to be require()d). The .js the caller needs is emitted
206
+ // separately by EmitWithPluginTransformers, so recover and skip the .d.ts for
207
+ // this run instead of aborting the whole build.
208
+ func emitDeclarations(prog *driver.Program, writeFile shimcompiler.WriteFile) {
209
+ defer func() {
210
+ if r := recover(); r != nil {
211
+ fmt.Fprintf(stderr, "ttsc-nestia build: declaration emit skipped (%v)\n", r)
212
+ }
213
+ }()
214
+ dts := prog.TSProgram.Emit(context.Background(), shimcompiler.EmitOptions{
215
+ EmitOnly: shimcompiler.EmitOnlyDts,
216
+ WriteFile: writeFile,
217
+ })
218
+ if dts != nil && dts.EmitSkipped {
219
+ fmt.Fprintln(stderr, "ttsc-nestia build: declaration emit skipped")
220
+ }
221
+ }
222
+
223
+ func runCheck(args []string) int {
224
+ fs := flag.NewFlagSet("check", flag.ContinueOnError)
225
+ fs.SetOutput(stderr)
226
+ tsconfigPath := fs.String("tsconfig", "tsconfig.json", "path to tsconfig.json")
227
+ cwdOverride := fs.String("cwd", "", "override the working directory")
228
+ pluginsJSON := fs.String("plugins-json", "", "ordered ttsc plugin payload")
229
+ if err := fs.Parse(args); err != nil {
230
+ return 2
231
+ }
232
+ if _, err := plugin.ParsePlan(*pluginsJSON); err != nil {
233
+ fmt.Fprintf(stderr, "ttsc-nestia check: %v\n", err)
234
+ return 2
235
+ }
236
+ cwd, ok := resolveCWD("ttsc-nestia check", *cwdOverride)
237
+ if !ok {
238
+ return 2
239
+ }
240
+ prog, diags, err := driver.LoadProgram(cwd, *tsconfigPath, driver.LoadProgramOptions{
241
+ ForceNoEmit: true,
242
+ })
243
+ if err != nil {
244
+ fmt.Fprintf(stderr, "ttsc-nestia check: %v\n", err)
245
+ return 2
246
+ }
247
+ if len(diags) > 0 {
248
+ driver.WritePrettyDiagnostics(stderr, diags, cwd)
249
+ return 2
250
+ }
251
+ defer prog.Close()
252
+ if diags := prog.Diagnostics(); len(diags) > 0 {
253
+ driver.WritePrettyDiagnostics(stderr, diags, cwd)
254
+ return 2
255
+ }
256
+ return 0
257
+ }
258
+
259
+ type Diagnostic struct {
260
+ File string
261
+ Line int
262
+ Column int
263
+ Code string
264
+ Message string
265
+ }
266
+
267
+ func (d Diagnostic) String(cwd string) string {
268
+ file := d.File
269
+ if rel, err := filepath.Rel(cwd, file); err == nil {
270
+ file = rel
271
+ }
272
+ if d.Line > 0 {
273
+ return fmt.Sprintf("%s:%d:%d - error TS(%s): %s", file, d.Line, d.Column, d.Code, d.Message)
274
+ }
275
+ return fmt.Sprintf("%s - error TS(%s): %s", file, d.Code, d.Message)
276
+ }
277
+
278
+ func WriteTypiaTransformDiagnostics(out io.Writer, diagnostics []Diagnostic, cwd string) {
279
+ for _, diag := range diagnostics {
280
+ fmt.Fprintln(out, diag.String(cwd))
281
+ }
282
+ }
283
+
284
+ func profileBuildStep(enabled bool, name string, started time.Time) {
285
+ if enabled {
286
+ profileBuildDuration(enabled, name, time.Since(started))
287
+ }
288
+ }
289
+
290
+ func profileBuildStepCount(enabled bool, name string, started time.Time, count int) {
291
+ if enabled {
292
+ fmt.Fprintf(stderr, "ttsc-nestia profile: %s=%s count=%d\n", name, time.Since(started), count)
293
+ }
294
+ }
295
+
296
+ func profileBuildDuration(enabled bool, name string, elapsed time.Duration) {
297
+ if enabled {
298
+ fmt.Fprintf(stderr, "ttsc-nestia profile: %s=%s\n", name, elapsed)
299
+ }
300
+ }
301
+
302
+ func NewDiagnostic(site typiaadapter.CallSite, message string) Diagnostic {
303
+ line, column := 0, 0
304
+ if site.File != nil && site.Call != nil {
305
+ pos := site.Call.AsNode().Pos()
306
+ if pos >= 0 {
307
+ l, c := shimscanner.GetECMALineAndByteOffsetOfPosition(site.File, pos)
308
+ line, column = l+1, c+1
309
+ }
310
+ }
311
+ return Diagnostic{
312
+ File: site.FilePath,
313
+ Line: line,
314
+ Column: column,
315
+ Code: "typia." + site.Module + "." + site.Method,
316
+ Message: message,
317
+ }
318
+ }
319
+
320
+ func typiaBuildRewriteSortKey(site typiaadapter.CallSite) int {
321
+ node := site.Call.AsNode()
322
+ if node == nil {
323
+ return 0
324
+ }
325
+ insideDecorator := false
326
+ classEnd := 0
327
+ for current := node.Parent; current != nil; current = current.Parent {
328
+ if current.Kind == NestiaCoreKindDecorator {
329
+ insideDecorator = true
330
+ }
331
+ if current.Kind == shimast.KindClassDeclaration || current.Kind == shimast.KindClassExpression {
332
+ classEnd = current.End()
333
+ break
334
+ }
335
+ }
336
+ if insideDecorator == false {
337
+ return node.Pos()
338
+ }
339
+ if classEnd != 0 {
340
+ return classEnd + node.Pos()
341
+ }
342
+ return node.Pos() + 1_000_000_000
343
+ }
344
+
345
+ func readTypiaPluginOptions(cwd, tsconfigPath string) typiaadapter.PluginOptions {
346
+ path := tsconfigPath
347
+ if !filepath.IsAbs(path) {
348
+ path = filepath.Join(cwd, path)
349
+ }
350
+ data, err := os.ReadFile(path)
351
+ if err != nil {
352
+ return typiaadapter.PluginOptions{}
353
+ }
354
+ text := string(data)
355
+ return typiaadapter.PluginOptions{
356
+ Functional: typiaOptionFunctionalPattern.MatchString(text),
357
+ Numeric: typiaOptionNumericPattern.MatchString(text),
358
+ Finite: typiaOptionFinitePattern.MatchString(text),
359
+ Undefined: typiaOptionUndefinedPattern.MatchString(text),
360
+ }
361
+ }
362
+
363
+ var (
364
+ typiaOptionFunctionalPattern = regexp.MustCompile(`(?s)"functional"\s*:\s*true`)
365
+ typiaOptionNumericPattern = regexp.MustCompile(`(?s)"numeric"\s*:\s*true`)
366
+ typiaOptionFinitePattern = regexp.MustCompile(`(?s)"finite"\s*:\s*true`)
367
+ typiaOptionUndefinedPattern = regexp.MustCompile(`(?s)"undefined"\s*:\s*true`)
368
+ )
369
+
370
+ func resolveCWD(label string, cwdOverride string) (string, bool) {
371
+ if cwdOverride != "" {
372
+ return cwdOverride, true
373
+ }
374
+ cwd, err := os.Getwd()
375
+ if err != nil {
376
+ fmt.Fprintf(stderr, "%s: cwd: %v\n", label, err)
377
+ return "", false
378
+ }
379
+ return cwd, true
380
+ }