ttsc 0.31.0 → 0.31.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 (155) hide show
  1. package/cmd/platform/main.go +2 -2
  2. package/internal/sourceprocess/contain_windows.go +10 -13
  3. package/internal/sourceprocess/started_windows.go +41 -0
  4. package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
  5. package/internal/watchhelper/run.go +30 -20
  6. package/internal/watchhelper/run_other.go +4 -4
  7. package/internal/watchhelper/run_windows.go +471 -0
  8. package/internal/watchhelper/watchhelper.go +8 -2
  9. package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
  10. package/lib/compiler/internal/build/BuildExecution.d.ts +4 -2
  11. package/lib/compiler/internal/build/BuildExecution.js +40 -12
  12. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  13. package/lib/compiler/internal/startResidentTransform.js +5 -4
  14. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  15. package/lib/compiler/internal/transformProjectInMemory.js +9 -7
  16. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  17. package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
  18. package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
  19. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  20. package/lib/internal/nativeBinary.d.ts +3 -3
  21. package/lib/internal/nativeBinary.js +3 -3
  22. package/lib/internal/pluginSource.d.ts +1 -0
  23. package/lib/internal/pluginSource.js +1 -0
  24. package/lib/internal/pluginSource.js.map +1 -1
  25. package/lib/internal/resolveNodeBinary.d.ts +6 -1
  26. package/lib/internal/resolveNodeBinary.js +6 -2
  27. package/lib/internal/resolveNodeBinary.js.map +1 -1
  28. package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
  29. package/lib/internal/runtimeExecutableIdentity.js +192 -102
  30. package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
  31. package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
  32. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  33. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
  34. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
  35. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
  36. package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
  37. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  38. package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
  39. package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
  40. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  41. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
  42. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
  43. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  44. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +24 -14
  45. package/lib/plugin/internal/load/loadProjectPlugins.js +141 -56
  46. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  47. package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
  48. package/lib/plugin/internal/readCapabilityResolution.js +8 -5
  49. package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
  50. package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +48 -0
  51. package/lib/plugin/internal/source/GoEnvironmentReading.js +156 -0
  52. package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
  53. package/lib/plugin/internal/source/NativeSourcePackages.d.ts +51 -13
  54. package/lib/plugin/internal/source/NativeSourcePackages.js +182 -27
  55. package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
  56. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +42 -3
  57. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +68 -7
  58. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  59. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
  60. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
  61. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
  62. package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
  63. package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
  64. package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
  65. package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
  66. package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
  67. package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
  68. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
  69. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
  70. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  71. package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +53 -19
  72. package/lib/plugin/internal/source/SourcePluginWorkspace.js +111 -22
  73. package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
  74. package/lib/plugin/internal/source/buildSourcePlugin.d.ts +34 -18
  75. package/lib/plugin/internal/source/buildSourcePlugin.js +52 -24
  76. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  77. package/lib/plugin/internal/source/computeCacheKey.d.ts +25 -7
  78. package/lib/plugin/internal/source/computeCacheKey.js +85 -34
  79. package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
  80. package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
  81. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +75 -102
  82. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
  83. package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
  84. package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
  85. package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
  86. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
  87. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
  88. package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
  89. package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
  90. package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
  91. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
  92. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
  93. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  94. package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
  95. package/lib/plugin/internal/source/pluginSourceState.js +20 -8
  96. package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
  97. package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
  98. package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
  99. package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
  100. package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
  101. package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
  102. package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
  103. package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
  104. package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
  105. package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
  106. package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
  107. package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
  108. package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
  109. package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
  110. package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
  111. package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
  112. package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
  113. package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
  114. package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
  115. package/lib/plugin/resolveCapabilityPlugins.js +67 -15
  116. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
  117. package/package.json +8 -8
  118. package/src/compiler/internal/build/BuildExecution.ts +46 -13
  119. package/src/compiler/internal/startResidentTransform.ts +12 -3
  120. package/src/compiler/internal/transformProjectInMemory.ts +10 -2
  121. package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
  122. package/src/internal/nativeBinary.ts +3 -3
  123. package/src/internal/pluginSource.ts +1 -0
  124. package/src/internal/resolveNodeBinary.ts +12 -1
  125. package/src/internal/runtimeExecutableIdentity.ts +227 -141
  126. package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
  127. package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
  128. package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
  129. package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
  130. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
  131. package/src/plugin/internal/load/loadProjectPlugins.ts +421 -211
  132. package/src/plugin/internal/readCapabilityResolution.ts +17 -6
  133. package/src/plugin/internal/source/GoEnvironmentReading.ts +178 -0
  134. package/src/plugin/internal/source/NativeSourcePackages.ts +365 -48
  135. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +83 -5
  136. package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
  137. package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
  138. package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
  139. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
  140. package/src/plugin/internal/source/SourcePluginWorkspace.ts +148 -25
  141. package/src/plugin/internal/source/buildSourcePlugin.ts +104 -36
  142. package/src/plugin/internal/source/computeCacheKey.ts +129 -55
  143. package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +152 -105
  144. package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
  145. package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
  146. package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
  147. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
  148. package/src/plugin/internal/source/pluginSourceState.ts +28 -12
  149. package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
  150. package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
  151. package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
  152. package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
  153. package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
  154. package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
  155. package/src/plugin/resolveCapabilityPlugins.ts +87 -13
@@ -0,0 +1,506 @@
1
+ //go:build windows
2
+
3
+ package watchhelper
4
+
5
+ import (
6
+ "bufio"
7
+ "bytes"
8
+ "encoding/binary"
9
+ "encoding/json"
10
+ "io"
11
+ "os"
12
+ "path/filepath"
13
+ "runtime"
14
+ "strings"
15
+ "testing"
16
+ "time"
17
+
18
+ "golang.org/x/sys/windows"
19
+ )
20
+
21
+ // TestWindowsNotificationsPreserveMutationsWithoutAccessNoise verifies the
22
+ // actual native notification boundary without a runtime or compiler child.
23
+ // A delivered marker establishes a positive stream frontier; elapsed quiet is
24
+ // never used to certify that access or earlier writes were filtered correctly.
25
+ //
26
+ // 1. Force actual access-time updates and reads, then observe a native marker.
27
+ // 2. Preserve A-B-A writes, recursion, Unicode rename/removal and separate owners.
28
+ // 3. Refuse missing/file roots, classify literal malformed/lost batches as gaps,
29
+ // and stream ready/drain, subscriber joins/removal and EOF retirement.
30
+ //
31
+ // @evidence contracts/testing.md#behavioral-verification The actual Windows broker acquires handles, receives native notifications and emits decoded wire results; assertions distinguish access noise, real changes, recursion, independent registrations, failed acquisition, loss and actual streaming ready/drain/join/remove/EOF retirement.
32
+ // @evidence contracts/testing.md#independent-expectations Authored file names, native access timestamps and literal malformed notification bytes establish expectations independently of the implementation's mask or decoded results.
33
+ // @evidence contracts/testing.md#distinguishing-cases Real access-only updates contrast with content-preserving A-B-A writes; shallow and recursive registrations contrast on one native tree; live and removed owners, pre-join and shared mutations across actual request/reply frontiers, valid Unicode names and malformed/truncated batches distinguish coverage and loss.
34
+ // @evidence contracts/testing.md#execution-ownership This direct Go unit invokes the owning native broker and run in process using temporary files and memory streams; it installs no package and launches no compiler, runtime or helper child.
35
+ func TestWindowsNotificationsPreserveMutationsWithoutAccessNoise(t *testing.T) {
36
+ t.Run("access and ABA", func(t *testing.T) {
37
+ root := t.TempDir()
38
+ source := filepath.Join(root, "source.ts")
39
+ original := []byte("export const value = 1;\n")
40
+ if err := os.WriteFile(source, original, 0600); err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ h, output := newWindowsTestBroker(t)
44
+ if err := h.request(windowsBrokerRequest{Op: "add", ID: 1, AllEvents: true, Locations: []windowsWatchLocation{{Directory: root}}}); err != nil {
45
+ t.Fatal(err)
46
+ }
47
+ // SetFileTime with nil last-write changes access alone, unlike utimes,
48
+ // which can round or write modification time and legitimately notify.
49
+ native, _ := windows.UTF16PtrFromString(source)
50
+ handle, err := windows.CreateFile(native, windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, nil, windows.OPEN_EXISTING, 0, 0)
51
+ if err != nil {
52
+ t.Fatal(err)
53
+ }
54
+ access := windows.NsecToFiletime(time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano())
55
+ err = windows.SetFileTime(handle, nil, &access, nil)
56
+ var observed windows.ByHandleFileInformation
57
+ if err == nil {
58
+ err = windows.GetFileInformationByHandle(handle, &observed)
59
+ }
60
+ _ = windows.CloseHandle(handle)
61
+ if err != nil {
62
+ t.Fatal(err)
63
+ }
64
+ if observed.LastAccessTime != access {
65
+ t.Fatalf("native access update was not established: %+v", observed.LastAccessTime)
66
+ }
67
+ if _, err := os.ReadFile(source); err != nil {
68
+ t.Fatal(err)
69
+ }
70
+ rows := windowsTestFrontier(t, h, output, root, "after-access")
71
+ for _, row := range rows {
72
+ if row["filename"] == "source.ts" {
73
+ t.Fatalf("access-only update/read became mutation: %+v", row)
74
+ }
75
+ }
76
+ if err := os.WriteFile(source, []byte("export const value = 2;\n"), 0600); err != nil {
77
+ t.Fatal(err)
78
+ }
79
+ if err := os.WriteFile(source, original, 0600); err != nil {
80
+ t.Fatal(err)
81
+ }
82
+ rows = windowsTestFrontier(t, h, output, root, "after-ABA")
83
+ found := false
84
+ for _, row := range rows {
85
+ if row["filename"] == "source.ts" && row["eventType"] == "change" {
86
+ found = true
87
+ }
88
+ }
89
+ if !found {
90
+ t.Fatalf("actual A-B-A writes disappeared: %+v", rows)
91
+ }
92
+ current, err := os.ReadFile(source)
93
+ if err != nil || !bytes.Equal(current, original) {
94
+ t.Fatalf("authored source restoration: %q, %v", current, err)
95
+ }
96
+ })
97
+ t.Run("live native directory loss", func(t *testing.T) {
98
+ parent := t.TempDir()
99
+ root := filepath.Join(parent, "watched")
100
+ if err := os.Mkdir(root, 0700); err != nil {
101
+ t.Fatal(err)
102
+ }
103
+ h, output := newWindowsTestBroker(t)
104
+ if err := h.request(windowsBrokerRequest{Op: "add", ID: 1, AllEvents: true, Locations: []windowsWatchLocation{{Directory: root}}}); err != nil {
105
+ t.Fatal(err)
106
+ }
107
+ if err := os.Remove(root); err != nil {
108
+ t.Fatal(err)
109
+ }
110
+ until := time.Now().Add(10 * time.Second)
111
+ for {
112
+ if err := h.drain(); err != nil {
113
+ t.Fatal(err)
114
+ }
115
+ _ = h.out.Flush()
116
+ rows := decodeWindowsTestRows(t, output)
117
+ failed := false
118
+ for _, row := range rows {
119
+ failed = failed || row["failed"] == true
120
+ }
121
+ if failed {
122
+ break
123
+ }
124
+ if time.Now().After(until) {
125
+ t.Fatalf("deleted native directory retained silent authority: %+v", rows)
126
+ }
127
+ time.Sleep(time.Millisecond)
128
+ }
129
+ if len(h.locations) != 0 {
130
+ t.Fatal("failed native watch remained live")
131
+ }
132
+ })
133
+ t.Run("sharing recursion and retirement", func(t *testing.T) {
134
+ root := t.TempDir()
135
+ child := filepath.Join(root, "child")
136
+ if err := os.Mkdir(child, 0700); err != nil {
137
+ t.Fatal(err)
138
+ }
139
+ h, output := newWindowsTestBroker(t)
140
+ for _, id := range []int64{1, 2, 3} {
141
+ if err := h.request(windowsBrokerRequest{Op: "add", ID: id, AllEvents: true, Locations: []windowsWatchLocation{{Directory: root, Recursive: id == 3}}}); err != nil {
142
+ t.Fatal(err)
143
+ }
144
+ }
145
+ if len(h.locations) != 2 {
146
+ t.Fatalf("shallow owners must share while recursion remains distinct: %d", len(h.locations))
147
+ }
148
+ deep := filepath.Join(child, "한글.ts")
149
+ if err := os.WriteFile(deep, []byte("x"), 0600); err != nil {
150
+ t.Fatal(err)
151
+ }
152
+ rows := windowsTestFrontier(t, h, output, root, "after-deep")
153
+ found := false
154
+ for _, row := range rows {
155
+ if row["filename"] != filepath.Join("child", "한글.ts") {
156
+ continue
157
+ }
158
+ if row["id"] != float64(3) {
159
+ t.Fatalf("shallow owner heard deep entry: %+v", row)
160
+ }
161
+ found = true
162
+ }
163
+ if !found {
164
+ t.Fatalf("recursive Unicode creation missing: %+v", rows)
165
+ }
166
+ h.remove(1)
167
+ file := filepath.Join(root, "rename-before.ts")
168
+ renamed := filepath.Join(root, "rename-after.ts")
169
+ if err := os.WriteFile(file, []byte("x"), 0600); err != nil {
170
+ t.Fatal(err)
171
+ }
172
+ if err := os.Rename(file, renamed); err != nil {
173
+ t.Fatal(err)
174
+ }
175
+ if err := os.Remove(renamed); err != nil {
176
+ t.Fatal(err)
177
+ }
178
+ rows = windowsTestFrontier(t, h, output, root, "after-retirement")
179
+ old, next := false, false
180
+ for _, row := range rows {
181
+ if row["id"] == float64(1) {
182
+ t.Fatalf("retired owner heard later events: %+v", row)
183
+ }
184
+ if row["id"] != float64(2) || row["eventType"] != "rename" {
185
+ continue
186
+ }
187
+ old = old || row["filename"] == "rename-before.ts"
188
+ next = next || row["filename"] == "rename-after.ts"
189
+ }
190
+ if !old || !next {
191
+ t.Fatalf("remaining shared owner lost structural events: %+v", rows)
192
+ }
193
+ h.remove(2)
194
+ h.remove(3)
195
+ if len(h.locations) != 0 || len(h.registrations) != 0 {
196
+ t.Fatalf("last owner did not retire native registry")
197
+ }
198
+ })
199
+ t.Run("streaming ready drain sharing and EOF", func(t *testing.T) {
200
+ root := t.TempDir()
201
+ input, requests := io.Pipe()
202
+ responses, output := io.Pipe()
203
+ rows := make(chan map[string]any, 256)
204
+ scanResult := make(chan error, 1)
205
+ finished := make(chan int, 1)
206
+ stopScan := make(chan struct{})
207
+ go func() {
208
+ code := Run(input, output, io.Discard)
209
+ _ = input.Close()
210
+ _ = output.Close()
211
+ finished <- code
212
+ }()
213
+ go func() {
214
+ defer close(rows)
215
+ scanner := bufio.NewScanner(responses)
216
+ for scanner.Scan() {
217
+ var row map[string]any
218
+ if err := json.Unmarshal(scanner.Bytes(), &row); err != nil {
219
+ scanResult <- err
220
+ return
221
+ }
222
+ select {
223
+ case rows <- row:
224
+ case <-stopScan:
225
+ scanResult <- nil
226
+ return
227
+ }
228
+ }
229
+ scanResult <- scanner.Err()
230
+ }()
231
+ joined := false
232
+ scannerJoined := false
233
+ t.Cleanup(func() {
234
+ _ = requests.Close()
235
+ // Caller-owned pipe closure unblocks native output and the scanner on
236
+ // failure. Join both owned goroutines before temporary cleanup.
237
+ if !joined {
238
+ _ = responses.Close()
239
+ select {
240
+ case <-finished:
241
+ case <-time.After(10 * time.Second):
242
+ t.Error("streaming native owner did not retire after caller closure")
243
+ }
244
+ }
245
+ close(stopScan)
246
+ _ = responses.Close()
247
+ if !scannerJoined {
248
+ select {
249
+ case <-scanResult:
250
+ case <-time.After(10 * time.Second):
251
+ t.Error("protocol scanner did not retire after caller closure")
252
+ }
253
+ }
254
+ })
255
+ encoder := json.NewEncoder(requests)
256
+ send := func(request windowsBrokerRequest) {
257
+ t.Helper()
258
+ if err := encoder.Encode(request); err != nil {
259
+ t.Fatal(err)
260
+ }
261
+ }
262
+ observed := []map[string]any{}
263
+ until := func(predicate func(map[string]any) bool) {
264
+ t.Helper()
265
+ deadline := time.NewTimer(10 * time.Second)
266
+ defer deadline.Stop()
267
+ for {
268
+ select {
269
+ case row, open := <-rows:
270
+ if !open {
271
+ t.Fatal("protocol output ended before its positive frontier")
272
+ }
273
+ observed = append(observed, row)
274
+ if row["failed"] == true || row["gap"] == true {
275
+ t.Fatalf("unexpected native coverage loss: %+v", row)
276
+ }
277
+ if predicate(row) {
278
+ return
279
+ }
280
+ case <-deadline.C:
281
+ t.Fatalf("streaming native frontier stalled: %+v", observed)
282
+ }
283
+ }
284
+ }
285
+ ready := func(id int64) {
286
+ t.Helper()
287
+ send(windowsBrokerRequest{Op: "add", ID: id, AllEvents: true, Locations: []windowsWatchLocation{{Directory: root}}})
288
+ until(func(row map[string]any) bool { return row["id"] == float64(id) && row["ready"] == true })
289
+ }
290
+ drain := func(id int64) {
291
+ t.Helper()
292
+ send(windowsBrokerRequest{Op: "drain", ID: id})
293
+ until(func(row map[string]any) bool { return row["id"] == float64(id) && row["drained"] == true })
294
+ }
295
+ write := func(name string) {
296
+ t.Helper()
297
+ if err := os.WriteFile(filepath.Join(root, name), []byte("actual native mutation"), 0600); err != nil {
298
+ t.Fatal(err)
299
+ }
300
+ }
301
+ ready(1)
302
+ write("before-join.ts")
303
+ until(func(row map[string]any) bool { return row["id"] == float64(1) && row["filename"] == "before-join.ts" })
304
+ drain(100)
305
+ ready(2)
306
+ write("shared.ts")
307
+ seen := map[float64]bool{}
308
+ until(func(row map[string]any) bool {
309
+ if row["filename"] == "shared.ts" {
310
+ id, ok := row["id"].(float64)
311
+ if ok {
312
+ seen[id] = true
313
+ }
314
+ }
315
+ return seen[1] && seen[2]
316
+ })
317
+ drain(101)
318
+ for _, row := range observed {
319
+ if row["id"] == float64(2) && row["filename"] == "before-join.ts" {
320
+ t.Fatalf("new owner received a mutation before its observed join frontier: %+v", row)
321
+ }
322
+ }
323
+ send(windowsBrokerRequest{Op: "remove", ID: 1})
324
+ drain(102)
325
+ observed = nil
326
+ write("after-remove.ts")
327
+ until(func(row map[string]any) bool { return row["id"] == float64(2) && row["filename"] == "after-remove.ts" })
328
+ drain(103)
329
+ for _, row := range observed {
330
+ if row["id"] == float64(1) {
331
+ t.Fatalf("removed owner received a later native row: %+v", row)
332
+ }
333
+ }
334
+ if err := requests.Close(); err != nil {
335
+ t.Fatal(err)
336
+ }
337
+ select {
338
+ case code := <-finished:
339
+ joined = true
340
+ if code != 0 {
341
+ t.Fatalf("actual armed protocol did not retire successfully: %d", code)
342
+ }
343
+ case <-time.After(10 * time.Second):
344
+ t.Fatal("EOF did not retire the actual native loop")
345
+ }
346
+ err := <-scanResult
347
+ scannerJoined = true
348
+ if err != nil {
349
+ t.Fatalf("actual protocol framing failed: %v", err)
350
+ }
351
+ // A directory handle with no sharing cannot coexist with the helper's
352
+ // retired FILE_LIST_DIRECTORY handle. This checks actual native closure.
353
+ native, err := windows.UTF16PtrFromString(root)
354
+ if err != nil {
355
+ t.Fatal(err)
356
+ }
357
+ exclusive, err := windows.CreateFile(native, windows.FILE_LIST_DIRECTORY, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
358
+ if err != nil {
359
+ t.Fatalf("EOF retained a native directory owner: %v", err)
360
+ }
361
+ if err := windows.CloseHandle(exclusive); err != nil {
362
+ t.Fatal(err)
363
+ }
364
+ })
365
+
366
+ t.Run("failure loss and EOF", func(t *testing.T) {
367
+ root := t.TempDir()
368
+ file := filepath.Join(root, "file")
369
+ if err := os.WriteFile(file, []byte("x"), 0600); err != nil {
370
+ t.Fatal(err)
371
+ }
372
+ h, output := newWindowsTestBroker(t)
373
+ for index, location := range []string{filepath.Join(root, "missing"), file} {
374
+ if err := h.request(windowsBrokerRequest{Op: "add", ID: int64(index + 1), Locations: []windowsWatchLocation{{Directory: root}, {Directory: location}}}); err != nil {
375
+ t.Fatal(err)
376
+ }
377
+ }
378
+ _ = h.out.Flush()
379
+ rows := decodeWindowsTestRows(t, output)
380
+ if len(rows) != 2 || rows[0]["failed"] != true || rows[1]["failed"] != true || len(h.locations) != 0 {
381
+ t.Fatalf("failed partial acquisition retained success: %+v", rows)
382
+ }
383
+ for _, buffer := range [][]byte{nil, {0}, make([]byte, 12), windowsTestRecord(6, "x"), windowsTestRecord(3, "x")[:13]} {
384
+ if _, complete := decodeWindowsNotifications(buffer); complete {
385
+ t.Fatalf("malformed/lost native bytes became complete: %v", buffer)
386
+ }
387
+ }
388
+ valid := windowsTestRecord(3, "한글.ts")
389
+ decoded, complete := decodeWindowsNotifications(valid)
390
+ if !complete || len(decoded) != 1 || decoded[0].name != "한글.ts" || decoded[0].kind != "change" {
391
+ t.Fatalf("valid native record: %+v, %v", decoded, complete)
392
+ }
393
+ binary.LittleEndian.PutUint32(valid, 4)
394
+ if _, complete := decodeWindowsNotifications(valid); complete {
395
+ t.Fatal("overlapping native next offset accepted")
396
+ }
397
+ invalidName := windowsTestRecord(3, "x")
398
+ binary.LittleEndian.PutUint16(invalidName[12:], 0xd800)
399
+ if _, complete := decodeWindowsNotifications(invalidName); complete {
400
+ t.Fatal("unrepresentable native identity accepted")
401
+ }
402
+ request, _ := json.Marshal(windowsBrokerRequest{Op: "add", ID: 7, AllEvents: true, Locations: []windowsWatchLocation{{Directory: root}}})
403
+ var protocol bytes.Buffer
404
+ if code := run(strings.NewReader(string(request)+"\n"), &protocol, io.Discard); code != 0 {
405
+ t.Fatalf("EOF did not retire an armed native watch: %d", code)
406
+ }
407
+ if !strings.Contains(protocol.String(), `"ready":true`) {
408
+ t.Fatalf("actual native ready missing: %s", protocol.String())
409
+ }
410
+ })
411
+ }
412
+
413
+ // newWindowsTestBroker opens a real completion port and retires every pending
414
+ // overlapped read before the test can release its buffer memory.
415
+ func newWindowsTestBroker(t *testing.T) (*windowsBroker, *bytes.Buffer) {
416
+ t.Helper()
417
+ runtime.LockOSThread()
418
+ t.Cleanup(runtime.UnlockOSThread)
419
+ port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 1)
420
+ if err != nil {
421
+ t.Fatal(err)
422
+ }
423
+ output := &bytes.Buffer{}
424
+ out := bufio.NewWriter(output)
425
+ h := &windowsBroker{port: port, nextKey: 1, watches: map[uintptr]*windowsDirectoryWatch{}, locations: map[windowsWatchLocation]uintptr{}, registrations: map[int64]map[uintptr]struct{}{}, out: out, encoder: json.NewEncoder(out)}
426
+ t.Cleanup(func() {
427
+ for key, watch := range h.watches {
428
+ h.retire(key, watch)
429
+ }
430
+ for len(h.watches) != 0 {
431
+ if _, err := h.completion(windows.INFINITE); err != nil {
432
+ t.Errorf("native cleanup: %v", err)
433
+ break
434
+ }
435
+ }
436
+ _ = windows.CloseHandle(port)
437
+ })
438
+ return h, output
439
+ }
440
+
441
+ // windowsTestFrontier waits for an actual marker from every live registration,
442
+ // collecting all earlier native rows. Its deadline only refuses stalled delivery.
443
+ func windowsTestFrontier(t *testing.T, h *windowsBroker, output *bytes.Buffer, root, marker string) []map[string]any {
444
+ t.Helper()
445
+ if err := os.WriteFile(filepath.Join(root, marker), []byte("frontier"), 0600); err != nil {
446
+ t.Fatal(err)
447
+ }
448
+ rows := []map[string]any{}
449
+ seen := map[int64]bool{}
450
+ until := time.Now().Add(10 * time.Second)
451
+ for {
452
+ if err := h.drain(); err != nil {
453
+ t.Fatal(err)
454
+ }
455
+ if err := h.out.Flush(); err != nil {
456
+ t.Fatal(err)
457
+ }
458
+ batch := decodeWindowsTestRows(t, output)
459
+ rows = append(rows, batch...)
460
+ for _, row := range batch {
461
+ if row["filename"] == marker {
462
+ seen[int64(row["id"].(float64))] = true
463
+ }
464
+ if row["failed"] == true || row["gap"] == true {
465
+ t.Fatalf("unexpected lost coverage: %+v", row)
466
+ }
467
+ }
468
+ if len(seen) == len(h.registrations) {
469
+ return rows
470
+ }
471
+ if time.Now().After(until) {
472
+ t.Fatalf("native frontier stalled: %+v", rows)
473
+ }
474
+ time.Sleep(time.Millisecond)
475
+ }
476
+ }
477
+
478
+ // decodeWindowsTestRows reads actual encoded broker output and resets only the
479
+ // test's observation buffer, never backend state.
480
+ func decodeWindowsTestRows(t *testing.T, output *bytes.Buffer) []map[string]any {
481
+ t.Helper()
482
+ var rows []map[string]any
483
+ decoder := json.NewDecoder(bytes.NewReader(output.Bytes()))
484
+ for decoder.More() {
485
+ var row map[string]any
486
+ if err := decoder.Decode(&row); err != nil {
487
+ t.Fatal(err)
488
+ }
489
+ rows = append(rows, row)
490
+ }
491
+ output.Reset()
492
+ return rows
493
+ }
494
+
495
+ // windowsTestRecord authors one literal FILE_NOTIFY_INFORMATION layout.
496
+ func windowsTestRecord(action uint32, name string) []byte {
497
+ units, _ := windows.UTF16FromString(name)
498
+ units = units[:len(units)-1]
499
+ buffer := make([]byte, 12+len(units)*2)
500
+ binary.LittleEndian.PutUint32(buffer[4:], action)
501
+ binary.LittleEndian.PutUint32(buffer[8:], uint32(len(units)*2))
502
+ for i, unit := range units {
503
+ binary.LittleEndian.PutUint16(buffer[12+i*2:], unit)
504
+ }
505
+ return buffer
506
+ }
@@ -1,3 +1,4 @@
1
+ import { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
1
2
  import type { ITtscLoadedNativePlugin } from "../../../structures/internal/ITtscLoadedNativePlugin";
2
3
  import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
3
4
  import type { ITtscProjectInputSnapshot } from "../../../structures/internal/ITtscProjectInputSnapshot";
@@ -143,7 +144,7 @@ export declare namespace BuildExecution {
143
144
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The caught resolution failure is legitimate only for commands not requiring a project; ordinary compilation failures do not enter this bypass.
144
145
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain project-independent commands, existing-project behavior and null applicability; the catch comment states its deliberate broad premise.
145
146
  * @evidence contracts/portability.md#os-neutral-implementation Invocation cwd is resolved natively and spawnNative receives executable/argv separately with platform-aware environment merging.
146
- * @evidence contracts/performance.md#efficient-algorithms Flag selection scans forwarded argument/name text before config IO. An applicable lane includes native cwd/config resolution, binary and runtime capability lookup, argv/env composition, complete captured output and diagnostic normalization; one requested command does not bound its native duration or output bytes.
147
+ * @evidence contracts/performance.md#efficient-algorithms Flag selection scans forwarded argument/name text before config IO. An applicable lane includes native cwd/config resolution, binary lookup, argv/env composition without runtime capability work (#1726), complete captured output and diagnostic normalization; one requested command does not bound its native duration or output bytes.
147
148
  *
148
149
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work A project probe and effectful terminal invocation establish no retained or shared cross-request result.
149
150
  *
@@ -225,7 +226,7 @@ export declare namespace BuildExecution {
225
226
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported compiler options express the check contract without intercepting writes or replacing compiler APIs; spawn errors remain errors.
226
227
  * @evidence contracts/common.md#meaningful-documentation Native prose explains the no-emit use and load-bearing argument precedence.
227
228
  * @evidence contracts/portability.md#os-neutral-implementation spawnNative uses the selected executable, separate argv and project cwd; environment composition handles native variable aliases and output text decoding is explicit UTF-8.
228
- * @evidence contracts/performance.md#efficient-algorithms One argv composition includes delegated option-presence classification, threading/isolation text and argument copies. Environment composition can perform native runtime capability probes before the compiler launch; complete capture/decoding and diagnostic normalization follow. Work/storage depend on argument/env/path/report bytes and native child work, without an asserted measured cost ranking.
229
+ * @evidence contracts/performance.md#efficient-algorithms One argv composition includes delegated option-presence classification, threading/isolation text and argument copies. Environment composition only layers variables; the compiler reads no Node selection, so no runtime capability probe precedes the launch (#1726). Complete capture/decoding and diagnostic normalization follow. Work/storage depend on argument/env/path/report bytes and native child work, without an asserted measured cost ranking.
229
230
  *
230
231
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work This executes the requested compiler pass; equivalence with other phases must be established by orchestration rather than caching a result here.
231
232
  *
@@ -332,6 +333,7 @@ export declare namespace BuildExecution {
332
333
  projectGuard: readonly string[];
333
334
  };
334
335
  cwd: string;
336
+ identities?: PluginContentIdentities.Store | undefined;
335
337
  nativePlugins: ITtscLoadedNativePlugin[];
336
338
  pluginConfigDir: string | undefined;
337
339
  pluginSetupFailure: TtscBuildResult | undefined;
@@ -10,6 +10,7 @@ const createCanonicalTempDirectory_1 = require("../../../internal/createCanonica
10
10
  const resolveNodeBinary_1 = require("../../../internal/resolveNodeBinary");
11
11
  const hasProjectPluginEntries_1 = require("../../../plugin/internal/load/hasProjectPluginEntries");
12
12
  const loadProjectPlugins_1 = require("../../../plugin/internal/load/loadProjectPlugins");
13
+ const PluginContentIdentities_1 = require("../../../plugin/internal/source/PluginContentIdentities");
13
14
  const outputText_1 = require("../outputText");
14
15
  const readProjectConfig_1 = require("../project/readProjectConfig");
15
16
  const CompilerProjectSelection_1 = require("../project/CompilerProjectSelection");
@@ -68,14 +69,28 @@ var BuildExecution;
68
69
  * Merge extra environment variables over `process.env`, always injecting
69
70
  * `TTSC_NODE_BINARY` selected by the runtime capability owner. Explicit
70
71
  * compatible overrides can select a different Node executable; this is not
71
- * necessarily the current process binary.
72
+ * necessarily the current process binary. An invocation that loaded plugins
73
+ * passes its record store, so the runtime is proven from metadata instead of
74
+ * streamed again before every native spawn (#1723).
72
75
  */
73
- function mergeEnv(extra, cwd = process.cwd(), ...later) {
76
+ function mergeEnv(extra, cwd, identities, ...later) {
74
77
  const env = SidecarEnvironment_1.SidecarEnvironment.merge(process.env, extra, ...later);
75
- const node = (0, resolveNodeBinary_1.resolveNodeBinary)(env, cwd);
78
+ const node = (0, resolveNodeBinary_1.resolveNodeBinary)(env, cwd, identities);
76
79
  SidecarEnvironment_1.SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
77
80
  return env;
78
81
  }
82
+ /**
83
+ * Merge extra environment variables over `process.env` for a spawn of the
84
+ * selected TypeScript-Go compiler itself.
85
+ *
86
+ * WARNING (#1726): the compiler never reads `TTSC_NODE_BINARY`, which only
87
+ * plugin sidecars consume (`mergeEnv`). Selecting it streamed the 85 MB Node
88
+ * executable twice and probed it on every launch, plugins or not, about 0.2 s
89
+ * of a 0.5 s build without plugins.
90
+ */
91
+ function compilerEnv(extra) {
92
+ return SidecarEnvironment_1.SidecarEnvironment.merge(process.env, extra);
93
+ }
79
94
  /**
80
95
  * Build the environment for a native plugin spawn. Injects `TTSC_TSGO_BINARY`
81
96
  * and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv`, plus
@@ -107,7 +122,7 @@ var BuildExecution;
107
122
  : { TTSC_PLUGIN_CONFIG_DIR: execution.pluginConfigDir }),
108
123
  TTSC_TTSX_BINARY: process.env.TTSC_TTSX_BINARY ??
109
124
  node_path_1.default.join(__dirname, "..", "..", "..", "launcher", "ttsx.js"),
110
- }, execution.projectRoot, extra,
125
+ }, execution.projectRoot, execution.identities, extra,
111
126
  // The invocation's resolved compiler owns the final environment layer.
112
127
  { TTSC_TSGO_BINARY: execution.tsgo.binary });
113
128
  return composeNativePluginEnv(env, extra, execution, SidecarEnvironment_1.SidecarEnvironment.read(env, "TTSC_NODE_BINARY"), plugin, tsgoArgs);
@@ -353,7 +368,7 @@ var BuildExecution;
353
368
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The caught resolution failure is legitimate only for commands not requiring a project; ordinary compilation failures do not enter this bypass.
354
369
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain project-independent commands, existing-project behavior and null applicability; the catch comment states its deliberate broad premise.
355
370
  * @evidence contracts/portability.md#os-neutral-implementation Invocation cwd is resolved natively and spawnNative receives executable/argv separately with platform-aware environment merging.
356
- * @evidence contracts/performance.md#efficient-algorithms Flag selection scans forwarded argument/name text before config IO. An applicable lane includes native cwd/config resolution, binary and runtime capability lookup, argv/env composition, complete captured output and diagnostic normalization; one requested command does not bound its native duration or output bytes.
371
+ * @evidence contracts/performance.md#efficient-algorithms Flag selection scans forwarded argument/name text before config IO. An applicable lane includes native cwd/config resolution, binary lookup, argv/env composition without runtime capability work (#1726), complete captured output and diagnostic normalization; one requested command does not bound its native duration or output bytes.
357
372
  *
358
373
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work A project probe and effectful terminal invocation establish no retained or shared cross-request result.
359
374
  *
@@ -382,7 +397,7 @@ var BuildExecution;
382
397
  const tsgo = (0, resolveTsgo_1.resolveTsgo)({ ...options, cwd });
383
398
  const res = (0, spawnNative_1.spawnNative)(tsgo.binary, [...(options.passthrough ?? [])], {
384
399
  cwd,
385
- env: mergeEnv(options.env, cwd),
400
+ env: compilerEnv(options.env),
386
401
  encoding: "utf8",
387
402
  });
388
403
  if (res.error) {
@@ -612,7 +627,7 @@ var BuildExecution;
612
627
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported compiler options express the check contract without intercepting writes or replacing compiler APIs; spawn errors remain errors.
613
628
  * @evidence contracts/common.md#meaningful-documentation Native prose explains the no-emit use and load-bearing argument precedence.
614
629
  * @evidence contracts/portability.md#os-neutral-implementation spawnNative uses the selected executable, separate argv and project cwd; environment composition handles native variable aliases and output text decoding is explicit UTF-8.
615
- * @evidence contracts/performance.md#efficient-algorithms One argv composition includes delegated option-presence classification, threading/isolation text and argument copies. Environment composition can perform native runtime capability probes before the compiler launch; complete capture/decoding and diagnostic normalization follow. Work/storage depend on argument/env/path/report bytes and native child work, without an asserted measured cost ranking.
630
+ * @evidence contracts/performance.md#efficient-algorithms One argv composition includes delegated option-presence classification, threading/isolation text and argument copies. Environment composition only layers variables; the compiler reads no Node selection, so no runtime capability probe precedes the launch (#1726). Complete capture/decoding and diagnostic normalization follow. Work/storage depend on argument/env/path/report bytes and native child work, without an asserted measured cost ranking.
616
631
  *
617
632
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work This executes the requested compiler pass; equivalence with other phases must be established by orchestration rather than caching a result here.
618
633
  *
@@ -631,7 +646,7 @@ var BuildExecution;
631
646
  ...CompilerProjectSelection_1.CompilerProjectSelection.readGuard(execution.compilerSelection, options.passthrough),
632
647
  ], {
633
648
  cwd: execution.compilerSelection.compilerArgsCwd,
634
- env: mergeEnv(options.env, execution.projectRoot),
649
+ env: compilerEnv(options.env),
635
650
  encoding: "utf8",
636
651
  });
637
652
  if (res.error) {
@@ -655,7 +670,7 @@ var BuildExecution;
655
670
  * its single emitting run; unsupported or unstable relations stay unknown.
656
671
  */
657
672
  function runTsgoBuild(execution, options, args) {
658
- const env = mergeEnv(options.env, execution.projectRoot);
673
+ const env = compilerEnv(options.env);
659
674
  const userOptions = (0, readEffectiveCompilerOptions_1.readEffectiveCompilerOptions)(execution.project, options.passthrough, execution.tsgo.binary, env, execution.compilerSelection.compilerArgsCwd);
660
675
  const userListedEmitted = userOptions?.("listEmittedFiles") === true;
661
676
  const run = (commandArgs) => {
@@ -901,16 +916,28 @@ var BuildExecution;
901
916
  const tsgo = (0, resolveTsgo_1.resolveTsgo)({ ...options, cwd: projectRoot });
902
917
  let pluginSetupFailure;
903
918
  let nativePlugins = [];
919
+ // Opened only for an invocation that loads plugins, so a project without
920
+ // any never gains a cache directory; the load and every later native spawn
921
+ // of this invocation prove their unchanged inputs through it (#1722, #1723).
922
+ let identities;
904
923
  try {
905
924
  if (compilerSelection.inspectionError === undefined &&
906
925
  (0, hasProjectPluginEntries_1.hasProjectPluginEntries)(project, options.plugins)) {
926
+ const cacheDir = options.cacheDir ??
927
+ SidecarEnvironment_1.SidecarEnvironment.read(options.env, "TTSC_CACHE_DIR");
928
+ const env = (0, inheritedSidecarEnv_1.inheritedSidecarEnv)(options.env, options.binary);
929
+ identities = PluginContentIdentities_1.PluginContentIdentities.open({
930
+ projectRoot,
931
+ cacheDir,
932
+ env,
933
+ });
907
934
  nativePlugins = (0, loadProjectPlugins_1.loadProjectPlugins)({
908
935
  binary: (0, resolveBinary_1.resolveBinary)(options) ?? "",
909
- cacheDir: options.cacheDir ??
910
- SidecarEnvironment_1.SidecarEnvironment.read(options.env, "TTSC_CACHE_DIR"),
936
+ cacheDir,
911
937
  cwd,
912
938
  entries: options.plugins,
913
- env: (0, inheritedSidecarEnv_1.inheritedSidecarEnv)(options.env, options.binary),
939
+ env,
940
+ identities,
914
941
  onWatchInputs: options.onWatchInputs,
915
942
  pluginConfigDir: options.pluginConfigDir,
916
943
  projectRoot,
@@ -932,6 +959,7 @@ var BuildExecution;
932
959
  return {
933
960
  compilerSelection,
934
961
  cwd,
962
+ ...(identities === undefined ? {} : { identities }),
935
963
  nativePlugins,
936
964
  pluginConfigDir: (0, resolvePluginConfigDir_1.resolvePluginConfigDir)({
937
965
  cwd,