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.
- package/cmd/platform/main.go +2 -2
- package/internal/sourceprocess/contain_windows.go +10 -13
- package/internal/sourceprocess/started_windows.go +41 -0
- package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
- package/internal/watchhelper/run.go +30 -20
- package/internal/watchhelper/run_other.go +4 -4
- package/internal/watchhelper/run_windows.go +471 -0
- package/internal/watchhelper/watchhelper.go +8 -2
- package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
- package/lib/internal/nativeBinary.d.ts +3 -3
- package/lib/internal/nativeBinary.js +3 -3
- package/lib/plugin/internal/load/loadProjectPlugins.d.ts +12 -13
- package/lib/plugin/internal/load/loadProjectPlugins.js +61 -36
- package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
- package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +42 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js +110 -0
- package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
- package/lib/plugin/internal/source/NativeSourcePackages.d.ts +7 -7
- package/lib/plugin/internal/source/NativeSourcePackages.js +33 -20
- package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +21 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +36 -0
- package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
- package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
- package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +50 -19
- package/lib/plugin/internal/source/SourcePluginWorkspace.js +109 -22
- package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
- package/lib/plugin/internal/source/buildSourcePlugin.d.ts +19 -16
- package/lib/plugin/internal/source/buildSourcePlugin.js +27 -22
- package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/source/computeCacheKey.d.ts +4 -2
- package/lib/plugin/internal/source/computeCacheKey.js +15 -6
- package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +2 -68
- package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
- package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
- package/package.json +8 -8
- package/src/internal/nativeBinary.ts +3 -3
- package/src/plugin/internal/load/loadProjectPlugins.ts +209 -117
- package/src/plugin/internal/source/GoEnvironmentReading.ts +109 -0
- package/src/plugin/internal/source/NativeSourcePackages.ts +103 -43
- package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +42 -0
- package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
- package/src/plugin/internal/source/SourcePluginWorkspace.ts +143 -25
- package/src/plugin/internal/source/buildSourcePlugin.ts +62 -34
- package/src/plugin/internal/source/computeCacheKey.ts +26 -8
- package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +8 -76
- package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
package/cmd/platform/main.go
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// The real compiler and runner commands live in the JavaScript launchers so
|
|
4
4
|
// they can resolve the consuming project's `typescript` and
|
|
5
5
|
// plugin descriptors. This binary supplies version/platform metadata, and the
|
|
6
|
-
// hidden `__watch` command: the Linux directory notification
|
|
7
|
-
// unplugin adapter runs.
|
|
6
|
+
// hidden `__watch` command: the native Linux and Windows directory notification
|
|
7
|
+
// helper the unplugin adapter runs.
|
|
8
8
|
package main
|
|
9
9
|
|
|
10
10
|
import (
|
|
@@ -125,6 +125,7 @@ func runCommand(req request, cancel <-chan struct{}, out, errOut io.Writer) (com
|
|
|
125
125
|
targetPid := 0
|
|
126
126
|
targetHandle := windows.Handle(0)
|
|
127
127
|
var targetErr error
|
|
128
|
+
targetPending := false
|
|
128
129
|
innerResult := false
|
|
129
130
|
defer func() {
|
|
130
131
|
if targetHandle != 0 {
|
|
@@ -133,21 +134,17 @@ func runCommand(req request, cancel <-chan struct{}, out, errOut io.Writer) (com
|
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
}()
|
|
136
|
-
retainTarget := func() error {
|
|
137
|
-
|
|
137
|
+
retainTarget := func() (bool, error) {
|
|
138
|
+
target, pending, readErr := readWindowsStarted(filepath.Join(dir, "started.json"))
|
|
138
139
|
if readErr != nil {
|
|
139
|
-
return readErr
|
|
140
|
-
}
|
|
141
|
-
var target windowsStarted
|
|
142
|
-
if decodeErr := json.Unmarshal(data, &target); decodeErr != nil {
|
|
143
|
-
return decodeErr
|
|
140
|
+
return pending, readErr
|
|
144
141
|
}
|
|
145
142
|
retained, retainErr := retainWindowsTarget(target, job)
|
|
146
143
|
if retainErr != nil {
|
|
147
|
-
return retainErr
|
|
144
|
+
return false, retainErr
|
|
148
145
|
}
|
|
149
146
|
targetHandle, targetPid = retained, int(target.Pid)
|
|
150
|
-
return nil
|
|
147
|
+
return false, nil
|
|
151
148
|
}
|
|
152
149
|
for !finished {
|
|
153
150
|
select {
|
|
@@ -196,8 +193,8 @@ func runCommand(req request, cancel <-chan struct{}, out, errOut io.Writer) (com
|
|
|
196
193
|
}
|
|
197
194
|
}
|
|
198
195
|
if started && !running && !finished {
|
|
199
|
-
targetErr = retainTarget()
|
|
200
|
-
if targetErr != nil && !
|
|
196
|
+
targetPending, targetErr = retainTarget()
|
|
197
|
+
if targetErr != nil && !targetPending {
|
|
201
198
|
value = failure("EPROCESS", targetErr.Error())
|
|
202
199
|
finished = true
|
|
203
200
|
}
|
|
@@ -233,8 +230,8 @@ func runCommand(req request, cancel <-chan struct{}, out, errOut io.Writer) (com
|
|
|
233
230
|
// identity can still be retained here, before termination. Missing identity
|
|
234
231
|
// after START is unknown, even if the job subsequently becomes empty.
|
|
235
232
|
noTarget := !started || (innerResult && value.Version == 1 && value.Pid == 0 && value.Error != nil)
|
|
236
|
-
if started && targetHandle == 0 && !noTarget && (targetErr == nil ||
|
|
237
|
-
targetErr = retainTarget()
|
|
233
|
+
if started && targetHandle == 0 && !noTarget && (targetErr == nil || targetPending) {
|
|
234
|
+
targetPending, targetErr = retainTarget()
|
|
238
235
|
}
|
|
239
236
|
originalJoined := innerResult && targetPid > 0 && value.Pid == targetPid && value.Cleanup.DirectChildJoined
|
|
240
237
|
value.Cancelled = cancelled
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
//go:build windows
|
|
2
|
+
|
|
3
|
+
package sourceprocess
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"encoding/json"
|
|
7
|
+
"errors"
|
|
8
|
+
"os"
|
|
9
|
+
|
|
10
|
+
"golang.org/x/sys/windows"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// readWindowsStarted acquires the helper's suspended-target publication. An
|
|
14
|
+
// absent file or an actual Windows sharing violation means acquisition is
|
|
15
|
+
// pending, not that the helper failed or that any target was enrolled. Windows
|
|
16
|
+
// can briefly refuse reads while the complete file is atomically published.
|
|
17
|
+
// The command owner continues observing helper exit, cancellation and deadline.
|
|
18
|
+
//
|
|
19
|
+
// WARNING (#1718): keep pending file acquisition separate from process identity
|
|
20
|
+
// validation. Malformed JSON and other I/O failures refuse immediately; even
|
|
21
|
+
// readable bytes grant no RUN permission. retainWindowsTarget must still obtain
|
|
22
|
+
// the original process by PID, creation identity and owned Job membership.
|
|
23
|
+
// Never classify its native errors as a pending publication, fabricate a joined
|
|
24
|
+
// target from an empty Job, or replace identity proof with a retry grace period.
|
|
25
|
+
//
|
|
26
|
+
// Each attempt reads and decodes the current complete bytes with no historical
|
|
27
|
+
// positive cache. Work and temporary memory scale with receipt bytes, and the
|
|
28
|
+
// native file handle closes within os.ReadFile. Only the owning command loop
|
|
29
|
+
// retries; final retirement still reports unknown if original authority could
|
|
30
|
+
// not be acquired. The Windows build constraint owns the native error code.
|
|
31
|
+
func readWindowsStarted(file string) (windowsStarted, bool, error) {
|
|
32
|
+
data, err := os.ReadFile(file)
|
|
33
|
+
if err != nil {
|
|
34
|
+
return windowsStarted{}, os.IsNotExist(err) || errors.Is(err, windows.ERROR_SHARING_VIOLATION), err
|
|
35
|
+
}
|
|
36
|
+
var started windowsStarted
|
|
37
|
+
if err := json.Unmarshal(data, &started); err != nil {
|
|
38
|
+
return windowsStarted{}, false, err
|
|
39
|
+
}
|
|
40
|
+
return started, false, nil
|
|
41
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//go:build windows
|
|
2
|
+
|
|
3
|
+
package sourceprocess
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"errors"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"testing"
|
|
10
|
+
|
|
11
|
+
"golang.org/x/sys/windows"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// TestWindowsStartedPublicationDistinguishesPendingFromRefusal verifies native
|
|
15
|
+
// publication acquisition without treating its readable bytes as enrollment.
|
|
16
|
+
//
|
|
17
|
+
// 1. Keep missing input pending, then lock a real complete receipt exclusively.
|
|
18
|
+
// 2. Require an actual sharing violation to remain pending and recover on close.
|
|
19
|
+
// 3. Refuse malformed bytes and a native directory read, then recover a repair.
|
|
20
|
+
//
|
|
21
|
+
// @evidence contracts/testing.md#behavioral-verification Actual Windows CreateFile with no sharing makes the maintained receipt reader fail with ERROR_SHARING_VIOLATION; releasing that handle yields the literal decoded record. Missing input stays pending, while malformed JSON and a real directory read refuse without pending authority. Repair is observed without historical success or failure caching.
|
|
22
|
+
// @evidence contracts/testing.md#independent-expectations Authored schema-one bytes declare PID 7 and creation 42 solely as decoding inputs; Windows' exclusive sharing contract supplies the expected native error. These literals do not certify a real process, which remains retainWindowsTarget's separate admission responsibility.
|
|
23
|
+
// @evidence contracts/testing.md#distinguishing-cases Missing and temporarily unreadable complete publications contrast with readable valid bytes, corrupt JSON and other native I/O refusal. Real release and repair distinguish acquisition recovery from a cached verdict. TestWindowsTargetOwnsSuspendedAdmission separately rejects foreign/missing PID, creation and Job identity; ordinary owned-process E2E covers actual RPC cancellation and cleanup.
|
|
24
|
+
// @evidence contracts/testing.md#execution-ownership This Windows package unit directly calls its owning reader with t.TempDir files and one real exclusive native handle, closed on every path before directory cleanup. It builds or starts no compiler, plugin, product host or consumer and does not replace filesystem/process methods.
|
|
25
|
+
func TestWindowsStartedPublicationDistinguishesPendingFromRefusal(t *testing.T) {
|
|
26
|
+
directory := t.TempDir()
|
|
27
|
+
file := filepath.Join(directory, "started.json")
|
|
28
|
+
if target, pending, err := readWindowsStarted(file); !pending || !os.IsNotExist(err) || target != (windowsStarted{}) {
|
|
29
|
+
t.Fatalf("missing publication: target=%+v pending=%t error=%v", target, pending, err)
|
|
30
|
+
}
|
|
31
|
+
data := []byte(`{"version":1,"pid":7,"created":42}`)
|
|
32
|
+
if err := os.WriteFile(file, data, 0600); err != nil {
|
|
33
|
+
t.Fatal(err)
|
|
34
|
+
}
|
|
35
|
+
name, err := windows.UTF16PtrFromString(file)
|
|
36
|
+
if err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
handle, err := windows.CreateFile(name, windows.GENERIC_READ, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
|
40
|
+
if err != nil {
|
|
41
|
+
t.Fatal(err)
|
|
42
|
+
}
|
|
43
|
+
defer func() {
|
|
44
|
+
if handle != 0 {
|
|
45
|
+
if err := windows.CloseHandle(handle); err != nil {
|
|
46
|
+
t.Error(err)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}()
|
|
50
|
+
if target, pending, err := readWindowsStarted(file); !pending || !errors.Is(err, windows.ERROR_SHARING_VIOLATION) || target != (windowsStarted{}) {
|
|
51
|
+
t.Fatalf("locked publication: target=%+v pending=%t error=%v", target, pending, err)
|
|
52
|
+
}
|
|
53
|
+
if err := windows.CloseHandle(handle); err != nil {
|
|
54
|
+
t.Fatal(err)
|
|
55
|
+
}
|
|
56
|
+
handle = 0
|
|
57
|
+
expected := windowsStarted{Version: 1, Pid: 7, Created: 42}
|
|
58
|
+
if target, pending, err := readWindowsStarted(file); err != nil || pending || target != expected {
|
|
59
|
+
t.Fatalf("released publication: target=%+v pending=%t error=%v", target, pending, err)
|
|
60
|
+
}
|
|
61
|
+
if err := os.WriteFile(file, []byte(`{"version":1`), 0600); err != nil {
|
|
62
|
+
t.Fatal(err)
|
|
63
|
+
}
|
|
64
|
+
if target, pending, err := readWindowsStarted(file); err == nil || pending || target != (windowsStarted{}) {
|
|
65
|
+
t.Fatalf("malformed publication: target=%+v pending=%t error=%v", target, pending, err)
|
|
66
|
+
}
|
|
67
|
+
if _, pending, err := readWindowsStarted(directory); err == nil || pending {
|
|
68
|
+
t.Fatalf("directory refusal: pending=%t error=%v", pending, err)
|
|
69
|
+
}
|
|
70
|
+
if err := os.WriteFile(file, data, 0600); err != nil {
|
|
71
|
+
t.Fatal(err)
|
|
72
|
+
}
|
|
73
|
+
if target, pending, err := readWindowsStarted(file); err != nil || pending || target != expected {
|
|
74
|
+
t.Fatalf("repaired publication: target=%+v pending=%t error=%v", target, pending, err)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -2,31 +2,41 @@ package watchhelper
|
|
|
2
2
|
|
|
3
3
|
import "io"
|
|
4
4
|
|
|
5
|
-
// Run serves the protocol over stdin and stdout
|
|
5
|
+
// Run serves the platform watch protocol over caller-owned stdin and stdout.
|
|
6
6
|
// End of scanned input, including a scanner error, ends the request stream;
|
|
7
7
|
// backend failures or an output flush failure can also end service.
|
|
8
8
|
//
|
|
9
|
-
// One loop polls the
|
|
10
|
-
// after queueing each request.
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
9
|
+
// Linux uses the inotify protocol. One loop polls the instance and a wake pipe
|
|
10
|
+
// the stdin reader writes after queueing each request. A sync drains the instance
|
|
11
|
+
// to an observed empty read before answering. Windows uses the unplugin broker
|
|
12
|
+
// protocol. One OS-thread-locked completion loop drains currently queued native
|
|
13
|
+
// completions before a request, so a joining subscriber does not receive those
|
|
14
|
+
// older events. Neither frontier totally orders concurrent kernel writes and
|
|
15
|
+
// request arrival, nor guarantees no event arrives after the empty observation.
|
|
16
|
+
// Continuous activity can prolong either drain; elapsed quiet is never proof.
|
|
14
17
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// descriptor closure so a delayed reader cannot write to a reused descriptor.
|
|
18
|
+
// Windows excludes access-only notifications at native acquisition (#1719).
|
|
19
|
+
// NEVER discard an actual mutation by comparing its final hash or timestamps:
|
|
20
|
+
// edits restored to the original bytes still invalidate compiler input proof.
|
|
21
|
+
// Overflow, malformed notifications, directory loss and native failures also
|
|
22
|
+
// withdraw coverage. Linux retains its native loss and directory-loss semantics.
|
|
21
23
|
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
24
|
+
// The backend owns its native handles. Linux closes its inotify and wake
|
|
25
|
+
// descriptors on return. Windows cancels reads, retains each overlapped buffer
|
|
26
|
+
// until its completion, then closes the completion port. A mutex synchronizes
|
|
27
|
+
// reader wakes with closure, and queued sends are canceled on return. A reader
|
|
28
|
+
// can remain blocked on caller input after early backend failure: the command
|
|
29
|
+
// exits when Run returns, while a library caller must close that input. Blocking
|
|
30
|
+
// output also remains caller-owned; this function has no independent deadline.
|
|
31
|
+
//
|
|
32
|
+
// @evidence contracts/common.md#principled-implementation Native inotify/completion queues establish the actual observation frontier; loss and native failure withdraw coverage. Windows omits access semantics at subscription rather than erasing delivered mutations. A joining subscriber is attached only after currently queued completions were consumed, under the documented concurrent-write limit.
|
|
33
|
+
// @evidence contracts/common.md#clear-and-simple-design A stdin reader only decodes and queues requests; one backend loop owns watch state, ordered emission and synchronization. Platform protocol and kernel handles remain behind this native boundary.
|
|
34
|
+
// @evidence contracts/common.md#prohibited-implementation-shortcuts Supported native APIs supply watch capabilities; no runtime method or system access-time setting is changed. Equal final bytes do not excuse A-B-A writes, and no test event substitutes for native ready/drain authority.
|
|
35
|
+
// @evidence contracts/common.md#meaningful-documentation Native paragraphs state each protocol, frontier limits, mutation policy, EOF cleanup and caller stream ownership. The package documents protocol messages separately; private backend comments explain acquisition, fanout, loss and pending buffer ownership.
|
|
36
|
+
// @evidence contracts/portability.md#os-neutral-implementation Build constraints isolate Linux inotify/poll and Windows overlapped completion APIs. Paths remain native API inputs, not shell text. Other native helper platforms return explicit usage code 2; the unplugin macOS adapter owns its separate FSEvents broker.
|
|
37
|
+
// @evidence contracts/performance.md#efficient-algorithms Each native batch is decoded linearly in its bytes; fanout visits K subscribers and encoded name bytes. Sorting O(K log K) is shared until membership changes. Indexed native descriptors/keys serve removal and completion without userspace project traversal. Continuous activity can prolong drains; native calls and caller writes can block independently of event counts.
|
|
38
|
+
// @evidence contracts/performance.md#reuse-equivalent-work Linux descriptors and Windows exact directory/recursion keys share native observation among subscribers. Ordered subscriber IDs are invalidated on membership changes; each owner retains distinct readiness and event policy. One loop serializes mutation, and Windows completion keys are never reused for another watch.
|
|
39
|
+
// @evidence contracts/performance.md#bound-retention-and-release-resources Deferred cleanup cancels queued sends and closes owned handles, retaining Windows buffers until cancellation completion. A mutex prevents late wake operations against a closed/reused native handle. The request channel holds at most 256 values, scanner framing is limited to 16 MiB and native buffers use 64 KiB each. Active subscriptions/order slices have no numeric cap; removal/loss reclaim live entries, with retiring Windows entries retained until completion. Caller stream blocking has no independent bound. Linux callers must remove before reusing an ID for a different descriptor, or its earlier subscription lasts until loss or process exit.
|
|
30
40
|
func Run(stdin io.Reader, stdout io.Writer, stderr io.Writer) int {
|
|
31
41
|
return run(stdin, stdout, stderr)
|
|
32
42
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//go:build !linux
|
|
1
|
+
//go:build !linux && !windows
|
|
2
2
|
|
|
3
3
|
package watchhelper
|
|
4
4
|
|
|
@@ -7,10 +7,10 @@ import (
|
|
|
7
7
|
"io"
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
-
// run reports that the helper serves Linux
|
|
11
|
-
//
|
|
10
|
+
// run reports that the helper serves Linux and Windows. macOS uses the
|
|
11
|
+
// adapter's FSEvents broker instead.
|
|
12
12
|
// This alternative acquires no watch or reader task and returns usage code 2.
|
|
13
13
|
func run(stdin io.Reader, stdout io.Writer, stderr io.Writer) int {
|
|
14
|
-
fmt.Fprintln(stderr, "ttsc __watch: only Linux
|
|
14
|
+
fmt.Fprintln(stderr, "ttsc __watch: only Linux and Windows are supported")
|
|
15
15
|
return 2
|
|
16
16
|
}
|