ttsc 0.31.0 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) 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/internal/nativeBinary.d.ts +3 -3
  11. package/lib/internal/nativeBinary.js +3 -3
  12. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -13
  13. package/lib/plugin/internal/load/loadProjectPlugins.js +61 -36
  14. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  15. package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +42 -0
  16. package/lib/plugin/internal/source/GoEnvironmentReading.js +110 -0
  17. package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
  18. package/lib/plugin/internal/source/NativeSourcePackages.d.ts +7 -7
  19. package/lib/plugin/internal/source/NativeSourcePackages.js +33 -20
  20. package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
  21. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -0
  22. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +36 -0
  23. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  24. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
  25. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
  26. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
  27. package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +50 -19
  28. package/lib/plugin/internal/source/SourcePluginWorkspace.js +109 -22
  29. package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
  30. package/lib/plugin/internal/source/buildSourcePlugin.d.ts +19 -16
  31. package/lib/plugin/internal/source/buildSourcePlugin.js +27 -22
  32. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  33. package/lib/plugin/internal/source/computeCacheKey.d.ts +4 -2
  34. package/lib/plugin/internal/source/computeCacheKey.js +15 -6
  35. package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
  36. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +2 -68
  37. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
  38. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
  39. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
  40. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  41. package/package.json +8 -8
  42. package/src/internal/nativeBinary.ts +3 -3
  43. package/src/plugin/internal/load/loadProjectPlugins.ts +209 -117
  44. package/src/plugin/internal/source/GoEnvironmentReading.ts +109 -0
  45. package/src/plugin/internal/source/NativeSourcePackages.ts +103 -43
  46. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +42 -0
  47. package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
  48. package/src/plugin/internal/source/SourcePluginWorkspace.ts +143 -25
  49. package/src/plugin/internal/source/buildSourcePlugin.ts +62 -34
  50. package/src/plugin/internal/source/computeCacheKey.ts +26 -8
  51. package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +8 -76
  52. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
@@ -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
+ }
@@ -4,8 +4,8 @@
4
4
  * The rules that find ttsc's platform binary (an absolute `TTSC_BINARY`, the
5
5
  * per-platform package, then the repository's own dev layout) are ttsc's alone.
6
6
  * A tool host that runs one of that binary's helper commands, as
7
- * `@ttsc/unplugin` runs its Linux watch helper, imports them from here, so the
8
- * helper always comes from the binary ttsc itself uses and no consumer carries
9
- * a copy of the rules.
7
+ * `@ttsc/unplugin` runs its native watch helpers, imports them from here, so
8
+ * the helper always comes from the binary ttsc itself uses and no consumer
9
+ * carries a copy of the rules.
10
10
  */
11
11
  export { resolveBinary } from "../compiler/internal/resolveBinary";
@@ -7,9 +7,9 @@ exports.resolveBinary = void 0;
7
7
  * The rules that find ttsc's platform binary (an absolute `TTSC_BINARY`, the
8
8
  * per-platform package, then the repository's own dev layout) are ttsc's alone.
9
9
  * A tool host that runs one of that binary's helper commands, as
10
- * `@ttsc/unplugin` runs its Linux watch helper, imports them from here, so the
11
- * helper always comes from the binary ttsc itself uses and no consumer carries
12
- * a copy of the rules.
10
+ * `@ttsc/unplugin` runs its native watch helpers, imports them from here, so
11
+ * the helper always comes from the binary ttsc itself uses and no consumer
12
+ * carries a copy of the rules.
13
13
  */
14
14
  var resolveBinary_1 = require("../compiler/internal/resolveBinary");
15
15
  Object.defineProperty(exports, "resolveBinary", { enumerable: true, get: function () { return resolveBinary_1.resolveBinary; } });
@@ -12,19 +12,18 @@ import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITts
12
12
  * under generic host manifests while preserving source-relative input layout.
13
13
  * Main proposals must agree in their full owning module; linked proposals must
14
14
  * agree inside every selected transform host. Errors or disagreement terminate
15
- * the load rather than changing the proposed kind.
16
- * Watch callers first receive structurally selected source/module/contributor
17
- * repair interests, before metadata can reject them. Valid records then narrow
18
- * those interests to the exact build directories; interests alone never admit
19
- * a plugin, certify a kind or supply reusable source-state proof.
20
- * Returns the ordered native plugins, parsed project config, recorded
21
- * JavaScript-host inputs and unresolved selection candidates, and the keyed
22
- * state of reported Go source directories supplied to the builds
23
- * (`pluginSources`): each plugin's module root and each contributor's source,
24
- * with its state (`pluginSourceState`), the sources as the build read them
25
- * together with the environment a build there is keyed on. Directories within
26
- * ttsc's installed package, including its overlays and the fallback
27
- * linked-plugin host, are keyed but omitted from this report under the
15
+ * the load rather than changing the proposed kind. Watch callers first receive
16
+ * structurally selected source/module/contributor repair interests, before
17
+ * metadata can reject them. Valid records then narrow those interests to the
18
+ * exact build directories; interests alone never admit a plugin, certify a kind
19
+ * or supply reusable source-state proof. Returns the ordered native plugins,
20
+ * parsed project config, recorded JavaScript-host inputs and unresolved
21
+ * selection candidates, and the keyed state of reported Go source directories
22
+ * supplied to the builds (`pluginSources`): each plugin's module root and each
23
+ * contributor's source, with its state (`pluginSourceState`), the sources as
24
+ * the build read them together with the environment a build there is keyed on.
25
+ * Directories within ttsc's installed package, including its overlays and the
26
+ * fallback linked-plugin host, are keyed but omitted from this report under the
28
27
  * installed-package/version ownership policy. That policy does not prove the
29
28
  * installation cannot be edited in place. Descriptor completeness relies on the
30
29
  * runtime recorder's status and the descriptor's explicit external-read