@nestia/core 12.0.0-rc.2 → 12.0.0-rc.3

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 (80) hide show
  1. package/LICENSE +21 -21
  2. package/MIGRATION.md +138 -169
  3. package/lib/decorators/NoTransformConfigurationError.js +5 -5
  4. package/lib/decorators/NoTransformConfigurationError.js.map +1 -1
  5. package/native/cmd/ttsc-nestia/main.go +11 -11
  6. package/native/go.mod +32 -32
  7. package/native/go.sum +54 -54
  8. package/native/plugin/plan.go +102 -102
  9. package/native/transform/ast.go +32 -32
  10. package/native/transform/build.go +388 -380
  11. package/native/transform/cleanup.go +408 -408
  12. package/native/transform/contributor.go +97 -97
  13. package/native/transform/core_querify.go +231 -231
  14. package/native/transform/core_transform.go +1996 -1996
  15. package/native/transform/core_websocket.go +115 -115
  16. package/native/transform/exports.go +13 -13
  17. package/native/transform/mcp_transform.go +414 -414
  18. package/native/transform/node_transform.go +357 -357
  19. package/native/transform/path_rewrite.go +285 -285
  20. package/native/transform/printer.go +244 -244
  21. package/native/transform/rewrite.go +668 -668
  22. package/native/transform/run.go +73 -73
  23. package/native/transform/transform.go +336 -336
  24. package/native/transform/typia_fast.go +375 -352
  25. package/native/transform/typia_replacement.go +24 -24
  26. package/native/transform.cjs +43 -43
  27. package/package.json +9 -9
  28. package/src/adaptors/McpAdaptor.ts +276 -276
  29. package/src/adaptors/WebSocketAdaptor.ts +429 -429
  30. package/src/decorators/DynamicModule.ts +44 -44
  31. package/src/decorators/EncryptedBody.ts +97 -97
  32. package/src/decorators/EncryptedController.ts +40 -40
  33. package/src/decorators/EncryptedModule.ts +98 -98
  34. package/src/decorators/EncryptedRoute.ts +213 -213
  35. package/src/decorators/HumanRoute.ts +21 -21
  36. package/src/decorators/McpRoute.ts +154 -154
  37. package/src/decorators/NoTransformConfigurationError.ts +40 -40
  38. package/src/decorators/PlainBody.ts +76 -76
  39. package/src/decorators/SwaggerCustomizer.ts +97 -97
  40. package/src/decorators/SwaggerExample.ts +180 -180
  41. package/src/decorators/TypedBody.ts +57 -57
  42. package/src/decorators/TypedException.ts +147 -147
  43. package/src/decorators/TypedFormData.ts +187 -187
  44. package/src/decorators/TypedHeaders.ts +66 -66
  45. package/src/decorators/TypedParam.ts +77 -77
  46. package/src/decorators/TypedQuery.ts +234 -234
  47. package/src/decorators/TypedRoute.ts +198 -198
  48. package/src/decorators/WebSocketRoute.ts +242 -242
  49. package/src/decorators/doNotThrowTransformError.ts +5 -5
  50. package/src/decorators/internal/EncryptedConstant.ts +2 -2
  51. package/src/decorators/internal/IMcpRouteReflect.ts +40 -40
  52. package/src/decorators/internal/IWebSocketRouteReflect.ts +23 -23
  53. package/src/decorators/internal/get_path_and_querify.ts +94 -94
  54. package/src/decorators/internal/get_path_and_stringify.ts +110 -110
  55. package/src/decorators/internal/get_text_body.ts +16 -16
  56. package/src/decorators/internal/headers_to_object.ts +11 -11
  57. package/src/decorators/internal/is_request_body_undefined.ts +12 -12
  58. package/src/decorators/internal/load_controller.ts +94 -94
  59. package/src/decorators/internal/route_error.ts +43 -43
  60. package/src/decorators/internal/validate_request_body.ts +64 -64
  61. package/src/decorators/internal/validate_request_form_data.ts +67 -67
  62. package/src/decorators/internal/validate_request_headers.ts +76 -76
  63. package/src/decorators/internal/validate_request_query.ts +83 -83
  64. package/src/index.ts +5 -5
  65. package/src/module.ts +25 -25
  66. package/src/options/IRequestBodyValidator.ts +20 -20
  67. package/src/options/IRequestFormDataProps.ts +27 -27
  68. package/src/options/IRequestHeadersValidator.ts +22 -22
  69. package/src/options/IRequestQueryValidator.ts +20 -20
  70. package/src/options/IResponseBodyQuerifier.ts +25 -25
  71. package/src/options/IResponseBodyStringifier.ts +30 -30
  72. package/src/transform.ts +26 -26
  73. package/src/typings/Creator.ts +3 -3
  74. package/src/typings/get-function-location.d.ts +7 -7
  75. package/src/utils/ArrayUtil.ts +7 -7
  76. package/src/utils/ExceptionManager.ts +115 -115
  77. package/src/utils/Singleton.ts +16 -16
  78. package/src/utils/SourceFinder.ts +54 -54
  79. package/src/utils/VersioningStrategy.ts +27 -27
  80. package/README.md +0 -93
@@ -1,380 +1,388 @@
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
- }
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: readBooleanPluginOption(text, "undefined"),
360
+ }
361
+ }
362
+
363
+ func readBooleanPluginOption(text string, name string) *bool {
364
+ matched := regexp.MustCompile(`(?s)"` + regexp.QuoteMeta(name) + `"\s*:\s*(true|false)`).FindStringSubmatch(text)
365
+ if matched == nil {
366
+ return nil
367
+ }
368
+ value := matched[1] == "true"
369
+ return &value
370
+ }
371
+
372
+ var (
373
+ typiaOptionFunctionalPattern = regexp.MustCompile(`(?s)"functional"\s*:\s*true`)
374
+ typiaOptionNumericPattern = regexp.MustCompile(`(?s)"numeric"\s*:\s*true`)
375
+ typiaOptionFinitePattern = regexp.MustCompile(`(?s)"finite"\s*:\s*true`)
376
+ )
377
+
378
+ func resolveCWD(label string, cwdOverride string) (string, bool) {
379
+ if cwdOverride != "" {
380
+ return cwdOverride, true
381
+ }
382
+ cwd, err := os.Getwd()
383
+ if err != nil {
384
+ fmt.Fprintf(stderr, "%s: cwd: %v\n", label, err)
385
+ return "", false
386
+ }
387
+ return cwd, true
388
+ }