@ttsc/wasm 0.12.2 → 0.12.4
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/ttsc-wasm/main.go +101 -89
- package/cmd/ttsc-wasm/main_wasm.go +6 -4
- package/dist/ttsc.wasm +0 -0
- package/host/api.go +154 -145
- package/host/doc.go +12 -12
- package/host/host.go +236 -225
- package/host/host_native.go +5 -2
- package/host/plugin.go +31 -31
- package/lib/src/MemFS.d.ts +35 -0
- package/lib/src/MemFS.js +57 -4
- package/lib/src/MemFS.js.map +1 -1
- package/lib/src/api.d.ts +46 -13
- package/lib/src/api.js +7 -4
- package/lib/src/api.js.map +1 -1
- package/lib/src/index.d.ts +1 -1
- package/lib/src/index.js +10 -3
- package/lib/src/index.js.map +1 -1
- package/lib/src/instantiate.d.ts +10 -7
- package/lib/src/instantiate.js +20 -3
- package/lib/src/instantiate.js.map +1 -1
- package/package.json +2 -2
- package/shim-vendor/shim/ast/go.mod +9 -0
- package/shim-vendor/shim/ast/go.sum +20 -0
- package/shim-vendor/shim/ast/safe_text.go +86 -0
- package/shim-vendor/shim/ast/shim.go +24 -0
- package/shim-vendor/shim/ast/test/node_text_joins_multi_hop_qualified_name_test.go +32 -0
- package/shim-vendor/shim/ast/test/node_text_joins_one_hop_qualified_name_test.go +31 -0
- package/shim-vendor/shim/ast/test/node_text_returns_empty_for_nil_test.go +23 -0
- package/shim-vendor/shim/ast/test/node_text_returns_identifier_text_test.go +27 -0
- package/shim-vendor/shim/ast/test/node_text_skips_empty_qualified_left_test.go +30 -0
- package/shim-vendor/shim/checker/shim.go +35 -0
- package/shim-vendor/shim/core/shim.go +17 -0
- package/shim-vendor/shim/diagnosticwriter/shim.go +4 -0
- package/shim-vendor/shim/lsp/shim.go +3 -3
- package/shim-vendor/shim/printer/shim.go +20 -0
- package/src/MemFS.ts +92 -7
- package/src/api.ts +46 -13
- package/src/index.ts +1 -5
- package/src/instantiate.ts +37 -14
package/host/host.go
CHANGED
|
@@ -10,20 +10,20 @@
|
|
|
10
10
|
package host
|
|
11
11
|
|
|
12
12
|
import (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
"fmt"
|
|
14
|
+
"os"
|
|
15
|
+
"runtime"
|
|
16
|
+
"sync/atomic"
|
|
17
|
+
"syscall/js"
|
|
18
|
+
"time"
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
// Build metadata. Override at link time via
|
|
22
22
|
// `-ldflags "-X github.com/samchon/ttsc/packages/wasm/host.version=..."`.
|
|
23
23
|
var (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
version = "0.0.0-dev"
|
|
25
|
+
commit = "dev"
|
|
26
|
+
date = "unknown"
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
// API stability: experimental until v1.0; signatures may change between
|
|
@@ -44,153 +44,153 @@ var (
|
|
|
44
44
|
// A matching readiness resolver is invoked: `globalThis[`${apiName}Ready`]`.
|
|
45
45
|
// JS callers register this BEFORE go.run begins so they can await wasm boot.
|
|
46
46
|
func Expose(apiName string, cfg Config) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
plugins := map[string]Plugin{}
|
|
48
|
+
pluginNames := make([]string, 0, len(cfg.Plugins))
|
|
49
|
+
for _, p := range cfg.Plugins {
|
|
50
|
+
if p == nil {
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
name := p.Name()
|
|
54
|
+
if name == "" {
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
if _, dup := plugins[name]; dup {
|
|
58
|
+
panic(fmt.Sprintf("host.Expose: duplicate plugin name %q", name))
|
|
59
|
+
}
|
|
60
|
+
plugins[name] = p
|
|
61
|
+
pluginNames = append(pluginNames, name)
|
|
62
|
+
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
api := map[string]any{
|
|
65
|
+
"version": js.FuncOf(jsVersion),
|
|
66
|
+
"build": js.FuncOf(jsBuild),
|
|
67
|
+
"check": js.FuncOf(jsCheck),
|
|
68
|
+
"transform": js.FuncOf(jsTransform),
|
|
69
|
+
"plugin": js.FuncOf(jsPluginDispatch(plugins)),
|
|
70
|
+
"plugins": js.FuncOf(jsPluginsList(pluginNames)),
|
|
71
|
+
}
|
|
72
|
+
js.Global().Set(apiName, js.ValueOf(api))
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
if ready := js.Global().Get(apiName + "Ready"); ready.Type() == js.TypeFunction {
|
|
75
|
+
ready.Invoke()
|
|
76
|
+
}
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
// A perpetual idle goroutine keeps the wasm runtime alive without
|
|
79
|
+
// tripping Go's deadlock detector while syscall.fsCall callbacks are in
|
|
80
|
+
// flight. `select {}` alone is wrongly classified as "all goroutines
|
|
81
|
+
// asleep" the moment the FS path hands a request to JS.
|
|
82
|
+
go func() {
|
|
83
|
+
for {
|
|
84
|
+
time.Sleep(time.Hour)
|
|
85
|
+
}
|
|
86
|
+
}()
|
|
87
|
+
select {}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// jsVersion returns the build metadata for this wasm binary.
|
|
91
91
|
func jsVersion(this js.Value, args []js.Value) any {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
return js.ValueOf(map[string]any{
|
|
93
|
+
"version": version,
|
|
94
|
+
"commit": commit,
|
|
95
|
+
"date": date,
|
|
96
|
+
"go": runtime.Version(),
|
|
97
|
+
"goos": runtime.GOOS,
|
|
98
|
+
"goarch": runtime.GOARCH,
|
|
99
|
+
})
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// jsBuild → Promise<{ code, stdout, stderr, result }>. `result` carries the
|
|
103
103
|
// compile JSON; the surrounding shape mirrors plugin dispatch so JS callers
|
|
104
104
|
// can write one error-handling branch.
|
|
105
105
|
func jsBuild(this js.Value, args []js.Value) any {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
opts := optionsArg(args)
|
|
107
|
+
return makePromise(func() any {
|
|
108
|
+
return runProjectCommand(opts, Build)
|
|
109
|
+
})
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// jsCheck → Promise<{ code, stdout, stderr, result }>.
|
|
113
113
|
func jsCheck(this js.Value, args []js.Value) any {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
opts := optionsArg(args)
|
|
115
|
+
return makePromise(func() any {
|
|
116
|
+
return runProjectCommand(opts, Check)
|
|
117
|
+
})
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
// jsTransform → Promise<{ code, stdout, stderr, result }>.
|
|
121
121
|
func jsTransform(this js.Value, args []js.Value) any {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
opts := optionsArg(args)
|
|
123
|
+
return makePromise(func() any {
|
|
124
|
+
return runProjectCommand(opts, Transform)
|
|
125
|
+
})
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// jsPluginDispatch routes `api.plugin({ name, command, ...opts })` to the
|
|
129
129
|
// matching registered Plugin's Run. The returned shape is identical to the
|
|
130
130
|
// base endpoints so a JS caller has one result type to handle.
|
|
131
131
|
func jsPluginDispatch(plugins map[string]Plugin) func(this js.Value, args []js.Value) any {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
132
|
+
return func(this js.Value, args []js.Value) any {
|
|
133
|
+
opts := optionsArg(args)
|
|
134
|
+
name := stringProp(opts, "name")
|
|
135
|
+
command := stringProp(opts, "command")
|
|
136
|
+
argv := buildPluginArgv(opts)
|
|
137
|
+
plugin, ok := plugins[name]
|
|
138
|
+
if !ok {
|
|
139
|
+
return errorPromise(2, fmt.Sprintf("host: unknown plugin %q", name))
|
|
140
|
+
}
|
|
141
|
+
return makePromise(func() any {
|
|
142
|
+
res := runWithCapturedIO(func() int {
|
|
143
|
+
return plugin.Run(command, argv)
|
|
144
|
+
})
|
|
145
|
+
return js.ValueOf(map[string]any{
|
|
146
|
+
"code": res.Code,
|
|
147
|
+
"stdout": res.Stdout,
|
|
148
|
+
"stderr": res.Stderr,
|
|
149
|
+
"result": "",
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// jsPluginsList returns the registered plugin names so JS callers can probe
|
|
156
156
|
// the wasm's contents at boot time.
|
|
157
157
|
func jsPluginsList(names []string) func(this js.Value, args []js.Value) any {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
return func(this js.Value, args []js.Value) any {
|
|
159
|
+
out := make([]any, len(names))
|
|
160
|
+
for i, n := range names {
|
|
161
|
+
out[i] = n
|
|
162
|
+
}
|
|
163
|
+
return js.ValueOf(out)
|
|
164
|
+
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// runProjectCommand wraps the base build/check/transform endpoints into the
|
|
168
168
|
// uniform `{ code, stdout, stderr, result }` shape. The result string is the
|
|
169
169
|
// JSON the endpoint produced; JSON.parse it on the JS side.
|
|
170
170
|
func runProjectCommand(opts js.Value, fn func(cwd, tsconfig string) ([]byte, int, error)) any {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
171
|
+
cwd := stringProp(opts, "cwd")
|
|
172
|
+
tsconfig := stringProp(opts, "tsconfig")
|
|
173
|
+
if tsconfig == "" {
|
|
174
|
+
tsconfig = "tsconfig.json"
|
|
175
|
+
}
|
|
176
|
+
if cwd == "" {
|
|
177
|
+
return errorResponse(2, "host: \"cwd\" is required")
|
|
178
|
+
}
|
|
179
|
+
data, code, err := fn(cwd, tsconfig)
|
|
180
|
+
if err != nil {
|
|
181
|
+
return js.ValueOf(map[string]any{
|
|
182
|
+
"code": code,
|
|
183
|
+
"stdout": "",
|
|
184
|
+
"stderr": err.Error() + "\n",
|
|
185
|
+
"result": "",
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
return js.ValueOf(map[string]any{
|
|
189
|
+
"code": code,
|
|
190
|
+
"stdout": "",
|
|
191
|
+
"stderr": "",
|
|
192
|
+
"result": string(data),
|
|
193
|
+
})
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// buildPluginArgv translates the JS options object into a CLI-shaped argv. We
|
|
@@ -198,31 +198,31 @@ func runProjectCommand(opts js.Value, fn func(cwd, tsconfig string) ([]byte, int
|
|
|
198
198
|
// `name` / `command` slots which the host consumes itself. This is the same
|
|
199
199
|
// translation typia's wasm does for `--cwd / --tsconfig / --output / ...`.
|
|
200
200
|
func buildPluginArgv(opts js.Value) []string {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
201
|
+
out := []string{}
|
|
202
|
+
if opts.Type() != js.TypeObject {
|
|
203
|
+
return out
|
|
204
|
+
}
|
|
205
|
+
keys := js.Global().Get("Object").Call("keys", opts)
|
|
206
|
+
n := keys.Length()
|
|
207
|
+
for i := 0; i < n; i++ {
|
|
208
|
+
key := keys.Index(i).String()
|
|
209
|
+
switch key {
|
|
210
|
+
case "name", "command":
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
value := opts.Get(key)
|
|
214
|
+
switch value.Type() {
|
|
215
|
+
case js.TypeString:
|
|
216
|
+
out = append(out, "--"+key+"="+value.String())
|
|
217
|
+
case js.TypeBoolean:
|
|
218
|
+
if value.Bool() {
|
|
219
|
+
out = append(out, "--"+key)
|
|
220
|
+
}
|
|
221
|
+
case js.TypeNumber:
|
|
222
|
+
out = append(out, fmt.Sprintf("--%s=%v", key, value.Float()))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return out
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
// makePromise wraps a Go computation as a JS Promise. The executor
|
|
@@ -230,51 +230,58 @@ func buildPluginArgv(opts js.Value) []string {
|
|
|
230
230
|
// in a goroutine so the JS event loop can drive `fs.stat` and friends to
|
|
231
231
|
// completion before we ask Go to receive the callback.
|
|
232
232
|
func makePromise(work func() any) js.Value {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
233
|
+
var executor js.Func
|
|
234
|
+
executor = js.FuncOf(func(this js.Value, args []js.Value) any {
|
|
235
|
+
resolve := args[0]
|
|
236
|
+
reject := args[1]
|
|
237
|
+
go func() {
|
|
238
|
+
defer func() {
|
|
239
|
+
if r := recover(); r != nil {
|
|
240
|
+
err := js.Global().Get("Error").New(fmt.Sprintf("%v", r))
|
|
241
|
+
reject.Invoke(err)
|
|
242
|
+
}
|
|
243
|
+
executor.Release()
|
|
244
|
+
}()
|
|
245
|
+
resolve.Invoke(work())
|
|
246
|
+
}()
|
|
247
|
+
return nil
|
|
248
|
+
})
|
|
249
|
+
return js.Global().Get("Promise").New(executor)
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// errorPromise returns a pre-resolved Promise carrying an error response.
|
|
252
253
|
func errorPromise(code int, message string) js.Value {
|
|
253
|
-
|
|
254
|
+
return makePromise(func() any { return errorResponse(code, message) })
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
// errorResponse builds the uniform JS result object for error cases.
|
|
256
258
|
func errorResponse(code int, message string) any {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
return js.ValueOf(map[string]any{
|
|
260
|
+
"code": code,
|
|
261
|
+
"stdout": "",
|
|
262
|
+
"stderr": message + "\n",
|
|
263
|
+
"result": "",
|
|
264
|
+
})
|
|
263
265
|
}
|
|
264
266
|
|
|
267
|
+
// optionsArg returns the first JS argument if it is an object, otherwise an
|
|
268
|
+
// empty JS object. Guards all endpoint handlers against missing or wrong-typed
|
|
269
|
+
// argument lists.
|
|
265
270
|
func optionsArg(args []js.Value) js.Value {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
if len(args) == 0 || args[0].Type() != js.TypeObject {
|
|
272
|
+
return js.ValueOf(map[string]any{})
|
|
273
|
+
}
|
|
274
|
+
return args[0]
|
|
270
275
|
}
|
|
271
276
|
|
|
277
|
+
// stringProp reads a string property from a JS object. Returns "" if the
|
|
278
|
+
// property is absent or not a string.
|
|
272
279
|
func stringProp(obj js.Value, key string) string {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
280
|
+
v := obj.Get(key)
|
|
281
|
+
if v.Type() != js.TypeString {
|
|
282
|
+
return ""
|
|
283
|
+
}
|
|
284
|
+
return v.String()
|
|
278
285
|
}
|
|
279
286
|
|
|
280
287
|
// runWithCapturedIO redirects os.Stdout / os.Stderr to temp MemFS files for
|
|
@@ -287,63 +294,67 @@ func stringProp(obj js.Value, key string) string {
|
|
|
287
294
|
// supported on the wasm target. The temp-file approach works because the
|
|
288
295
|
// MemFS shim implements file open/write/read.
|
|
289
296
|
func runWithCapturedIO(task func() int) APIResult {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
prevOut, prevErr := os.Stdout, os.Stderr
|
|
298
|
+
// The defer is a safety net: if an early return skips the explicit restore
|
|
299
|
+
// below, os.Stdout/os.Stderr are still restored. The explicit restore that
|
|
300
|
+
// follows the task call is a no-op for the defer but makes the happy path
|
|
301
|
+
// readable without tracing the defer.
|
|
302
|
+
defer func() {
|
|
303
|
+
os.Stdout = prevOut
|
|
304
|
+
os.Stderr = prevErr
|
|
305
|
+
}()
|
|
295
306
|
|
|
296
|
-
|
|
297
|
-
|
|
307
|
+
stdoutPath := fmt.Sprintf("/tmp/ttsc-host-capture-%d-%d.stdout", os.Getpid(), captureCounter.Add(1))
|
|
308
|
+
stderrPath := fmt.Sprintf("/tmp/ttsc-host-capture-%d-%d.stderr", os.Getpid(), captureCounter.Add(1))
|
|
298
309
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
310
|
+
outFile, outErr := os.Create(stdoutPath)
|
|
311
|
+
errFile, errErr := os.Create(stderrPath)
|
|
312
|
+
if outErr != nil || errErr != nil {
|
|
313
|
+
// Fall back to the original streams. Better to lose capture than the
|
|
314
|
+
// call. The MemFS writeSync shim surfaces the failure to the host
|
|
315
|
+
// console so the regression is visible.
|
|
316
|
+
if outErr != nil {
|
|
317
|
+
fmt.Fprintf(prevErr, "host.runWithCapturedIO: stdout temp file failed: %v\n", outErr)
|
|
318
|
+
}
|
|
319
|
+
if errErr != nil {
|
|
320
|
+
fmt.Fprintf(prevErr, "host.runWithCapturedIO: stderr temp file failed: %v\n", errErr)
|
|
321
|
+
}
|
|
322
|
+
if outFile != nil {
|
|
323
|
+
_ = outFile.Close()
|
|
324
|
+
_ = os.Remove(stdoutPath)
|
|
325
|
+
}
|
|
326
|
+
if errFile != nil {
|
|
327
|
+
_ = errFile.Close()
|
|
328
|
+
_ = os.Remove(stderrPath)
|
|
329
|
+
}
|
|
330
|
+
return APIResult{Code: task()}
|
|
331
|
+
}
|
|
321
332
|
|
|
322
|
-
|
|
323
|
-
|
|
333
|
+
os.Stdout = outFile
|
|
334
|
+
os.Stderr = errFile
|
|
324
335
|
|
|
325
|
-
|
|
336
|
+
code := task()
|
|
326
337
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
338
|
+
// Sync + close BEFORE swapping back, so the plugin's last writes hit the
|
|
339
|
+
// file before we read it.
|
|
340
|
+
_ = outFile.Sync()
|
|
341
|
+
_ = errFile.Sync()
|
|
342
|
+
_ = outFile.Close()
|
|
343
|
+
_ = errFile.Close()
|
|
333
344
|
|
|
334
|
-
|
|
335
|
-
|
|
345
|
+
os.Stdout = prevOut
|
|
346
|
+
os.Stderr = prevErr
|
|
336
347
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
348
|
+
stdoutBytes, _ := os.ReadFile(stdoutPath)
|
|
349
|
+
stderrBytes, _ := os.ReadFile(stderrPath)
|
|
350
|
+
_ = os.Remove(stdoutPath)
|
|
351
|
+
_ = os.Remove(stderrPath)
|
|
341
352
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
353
|
+
return APIResult{
|
|
354
|
+
Code: code,
|
|
355
|
+
Stdout: string(stdoutBytes),
|
|
356
|
+
Stderr: string(stderrBytes),
|
|
357
|
+
}
|
|
347
358
|
}
|
|
348
359
|
|
|
349
360
|
// captureCounter avoids temp-file name collisions when plugin dispatches
|
package/host/host_native.go
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
//go:build !js
|
|
2
2
|
|
|
3
|
+
// Stub implementations of JS-only host helpers for non-wasm targets.
|
|
4
|
+
// This file keeps the package importable by `go build ./...` on linux/darwin
|
|
5
|
+
// without GOOS=js, which native CI jobs need.
|
|
3
6
|
package host
|
|
4
7
|
|
|
5
8
|
// Expose is a no-op on native targets. Consumers can `go build ./...` to
|
|
@@ -7,6 +10,6 @@ package host
|
|
|
7
10
|
// stays in scope but the body short-circuits so it's safe to call from a
|
|
8
11
|
// non-wasm sanity-test entrypoint.
|
|
9
12
|
func Expose(apiName string, cfg Config) {
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
_ = apiName
|
|
14
|
+
_ = cfg
|
|
12
15
|
}
|
package/host/plugin.go
CHANGED
|
@@ -13,47 +13,47 @@ package host
|
|
|
13
13
|
// A typical Plugin implementation in the consumer wasm is a thin adapter that
|
|
14
14
|
// forwards to the same Run* function the native sidecar's `main.go` calls:
|
|
15
15
|
//
|
|
16
|
-
//
|
|
16
|
+
// type bannerPlugin struct{}
|
|
17
17
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
18
|
+
// func (bannerPlugin) Name() string { return "@ttsc/banner" }
|
|
19
|
+
// func (bannerPlugin) Run(command string, args []string) int {
|
|
20
|
+
// switch command {
|
|
21
|
+
// case "build": return utility.RunBuild(args)
|
|
22
|
+
// case "check": return utility.RunCheck(args)
|
|
23
|
+
// case "transform": return utility.RunTransform(args)
|
|
24
|
+
// default: return 2
|
|
25
|
+
// }
|
|
26
|
+
// }
|
|
27
27
|
//
|
|
28
28
|
// The host installs the package-level writers in `runWithCapturedIO` before
|
|
29
29
|
// calling Run, so anything the plugin writes to ttsc's `stdout` / `stderr`
|
|
30
30
|
// streams is captured and returned to the JS caller.
|
|
31
31
|
type Plugin interface {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// Name is the npm-style plugin id (e.g. `@ttsc/banner`). The JS side
|
|
33
|
+
// passes this exact string when dispatching: `api.plugin("@ttsc/banner",
|
|
34
|
+
// "build", opts)`. Names must be unique within a Config.
|
|
35
|
+
Name() string
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
// Run dispatches a subcommand. `command` is the verb the JS caller asked
|
|
38
|
+
// for (typically build / check / transform / fix / format / version);
|
|
39
|
+
// `args` is the rest of the argv, already prefixed with `--flag=value`
|
|
40
|
+
// pairs the host built from the JS options object.
|
|
41
|
+
//
|
|
42
|
+
// Return the exit code (0 for success, 2 for usage errors, 3 for runtime
|
|
43
|
+
// errors — mirrors the native CLI exit-code contract). Anything written
|
|
44
|
+
// to `os.Stdout` / `os.Stderr` is captured by the host.
|
|
45
|
+
//
|
|
46
|
+
// API stability: experimental until v1.0; the signature is expected to
|
|
47
|
+
// change to `Run(ctx *PluginContext) int` in a follow-up release.
|
|
48
|
+
Run(command string, args []string) int
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// Config carries the optional registrations the host applies before binding
|
|
52
52
|
// `globalThis[name]`. Pass `Config{}` for a vanilla ttsc + tsgo wasm.
|
|
53
53
|
type Config struct {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
// Plugins are dispatched through `api.plugin(name, command, opts)` from
|
|
55
|
+
// JS. Their Run methods share the same `os.Stdout` / `os.Stderr` streams
|
|
56
|
+
// the base build/check/transform endpoints use, so diagnostics render
|
|
57
|
+
// the same way no matter which lane produced them.
|
|
58
|
+
Plugins []Plugin
|
|
59
59
|
}
|