goscript 0.1.0 → 0.1.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 (327) hide show
  1. package/README.md +267 -255
  2. package/cmd/goscript/cmd-test.go +193 -0
  3. package/cmd/goscript/cmd-test_test.go +76 -0
  4. package/cmd/goscript/main.go +1 -0
  5. package/compiler/build-flags.go +38 -0
  6. package/compiler/compile-request.go +2 -0
  7. package/compiler/compliance_test.go +0 -8
  8. package/compiler/gotest/owner.go +24 -0
  9. package/compiler/gotest/package-result.go +67 -0
  10. package/compiler/gotest/request.go +145 -0
  11. package/compiler/gotest/result.go +28 -0
  12. package/compiler/gotest/runner.go +588 -0
  13. package/compiler/gotest/runner_test.go +627 -0
  14. package/compiler/gotest/test.go +9 -0
  15. package/compiler/index.test.ts +1 -1
  16. package/compiler/lowered-program.go +71 -19
  17. package/compiler/lowering.go +5065 -569
  18. package/compiler/override-facts.go +307 -0
  19. package/compiler/override-registry.go +50 -189
  20. package/compiler/override-registry_test.go +47 -0
  21. package/compiler/package-graph.go +50 -27
  22. package/compiler/package-graph_test.go +37 -2
  23. package/compiler/package-test-function.go +9 -0
  24. package/compiler/package-test-graph-package.go +40 -0
  25. package/compiler/package-test-graph-variant.go +105 -0
  26. package/compiler/package-test-graph.go +117 -0
  27. package/compiler/package-test-graph_test.go +144 -0
  28. package/compiler/runtime-contract.go +189 -29
  29. package/compiler/runtime-contract_test.go +44 -30
  30. package/compiler/semantic-model-types.go +9 -6
  31. package/compiler/semantic-model.go +538 -38
  32. package/compiler/semantic-model_test.go +55 -0
  33. package/compiler/service.go +1 -1
  34. package/compiler/skeleton_test.go +679 -49
  35. package/compiler/tsworkspace/owner.go +334 -0
  36. package/compiler/tsworkspace/owner_test.go +93 -0
  37. package/compiler/tsworkspace/result.go +17 -0
  38. package/compiler/typescript-emitter.go +459 -82
  39. package/compiler/wasm/compile.go +1 -1
  40. package/compiler/wasm/compile_test.go +61 -11
  41. package/compiler/wasm_api.go +172 -7
  42. package/dist/gs/builtin/builtin.d.ts +20 -2
  43. package/dist/gs/builtin/builtin.js +194 -6
  44. package/dist/gs/builtin/builtin.js.map +1 -1
  45. package/dist/gs/builtin/channel.d.ts +8 -0
  46. package/dist/gs/builtin/channel.js +12 -0
  47. package/dist/gs/builtin/channel.js.map +1 -1
  48. package/dist/gs/builtin/slice.d.ts +22 -2
  49. package/dist/gs/builtin/slice.js +216 -44
  50. package/dist/gs/builtin/slice.js.map +1 -1
  51. package/dist/gs/builtin/type.d.ts +5 -2
  52. package/dist/gs/builtin/type.js +83 -24
  53. package/dist/gs/builtin/type.js.map +1 -1
  54. package/dist/gs/builtin/varRef.d.ts +5 -0
  55. package/dist/gs/builtin/varRef.js +23 -0
  56. package/dist/gs/builtin/varRef.js.map +1 -1
  57. package/dist/gs/bytes/buffer.gs.js +48 -44
  58. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  59. package/dist/gs/bytes/reader.gs.js +20 -18
  60. package/dist/gs/bytes/reader.gs.js.map +1 -1
  61. package/dist/gs/context/context.d.ts +5 -4
  62. package/dist/gs/context/context.js +10 -10
  63. package/dist/gs/context/context.js.map +1 -1
  64. package/dist/gs/crypto/internal/fips140deps/byteorder/index.d.ts +1 -0
  65. package/dist/gs/crypto/internal/fips140deps/byteorder/index.js +2 -0
  66. package/dist/gs/crypto/internal/fips140deps/byteorder/index.js.map +1 -0
  67. package/dist/gs/crypto/internal/fips140deps/godebug/index.d.ts +1 -0
  68. package/dist/gs/crypto/internal/fips140deps/godebug/index.js +2 -0
  69. package/dist/gs/crypto/internal/fips140deps/godebug/index.js.map +1 -0
  70. package/dist/gs/embed/index.d.ts +7 -0
  71. package/dist/gs/embed/index.js +16 -0
  72. package/dist/gs/embed/index.js.map +1 -0
  73. package/dist/gs/encoding/json/index.d.ts +1 -0
  74. package/dist/gs/encoding/json/index.js +18 -0
  75. package/dist/gs/encoding/json/index.js.map +1 -1
  76. package/dist/gs/errors/errors.d.ts +4 -0
  77. package/dist/gs/errors/errors.js +81 -0
  78. package/dist/gs/errors/errors.js.map +1 -1
  79. package/dist/gs/fmt/fmt.d.ts +4 -4
  80. package/dist/gs/fmt/fmt.js +42 -11
  81. package/dist/gs/fmt/fmt.js.map +1 -1
  82. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +35 -0
  83. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +211 -1
  84. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  85. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +189 -0
  86. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +825 -0
  87. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -0
  88. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.d.ts +163 -0
  89. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +449 -0
  90. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +1 -0
  91. package/dist/gs/github.com/klauspost/compress/internal/le/index.d.ts +9 -0
  92. package/dist/gs/github.com/klauspost/compress/internal/le/index.js +71 -0
  93. package/dist/gs/github.com/klauspost/compress/internal/le/index.js.map +1 -0
  94. package/dist/gs/go/internal/scannerhooks/index.d.ts +3 -0
  95. package/dist/gs/go/internal/scannerhooks/index.js +5 -0
  96. package/dist/gs/go/internal/scannerhooks/index.js.map +1 -0
  97. package/dist/gs/go/scanner/index.d.ts +13 -0
  98. package/dist/gs/go/scanner/index.js +35 -0
  99. package/dist/gs/go/scanner/index.js.map +1 -1
  100. package/dist/gs/go/token/index.d.ts +156 -0
  101. package/dist/gs/go/token/index.js +500 -4
  102. package/dist/gs/go/token/index.js.map +1 -1
  103. package/dist/gs/internal/abi/index.d.ts +4 -0
  104. package/dist/gs/internal/abi/index.js +10 -0
  105. package/dist/gs/internal/abi/index.js.map +1 -1
  106. package/dist/gs/internal/bytealg/index.d.ts +2 -0
  107. package/dist/gs/internal/bytealg/index.js +14 -0
  108. package/dist/gs/internal/bytealg/index.js.map +1 -1
  109. package/dist/gs/internal/byteorder/index.d.ts +8 -2
  110. package/dist/gs/internal/byteorder/index.js +56 -25
  111. package/dist/gs/internal/byteorder/index.js.map +1 -1
  112. package/dist/gs/internal/godebug/index.d.ts +12 -0
  113. package/dist/gs/internal/godebug/index.js +30 -0
  114. package/dist/gs/internal/godebug/index.js.map +1 -0
  115. package/dist/gs/io/fs/index.d.ts +1 -0
  116. package/dist/gs/io/fs/index.js +1 -0
  117. package/dist/gs/io/fs/index.js.map +1 -1
  118. package/dist/gs/io/fs/readlink.d.ts +8 -0
  119. package/dist/gs/io/fs/readlink.js +64 -0
  120. package/dist/gs/io/fs/readlink.js.map +1 -0
  121. package/dist/gs/io/fs/walk.d.ts +3 -3
  122. package/dist/gs/io/fs/walk.js +7 -7
  123. package/dist/gs/io/fs/walk.js.map +1 -1
  124. package/dist/gs/io/io.d.ts +40 -6
  125. package/dist/gs/io/io.js +151 -26
  126. package/dist/gs/io/io.js.map +1 -1
  127. package/dist/gs/maps/iter.d.ts +3 -3
  128. package/dist/gs/maps/iter.js +3 -3
  129. package/dist/gs/maps/iter.js.map +1 -1
  130. package/dist/gs/maps/maps.d.ts +2 -2
  131. package/dist/gs/maps/maps.js +1 -1
  132. package/dist/gs/maps/maps.js.map +1 -1
  133. package/dist/gs/math/bits/index.d.ts +13 -4
  134. package/dist/gs/math/bits/index.js +66 -34
  135. package/dist/gs/math/bits/index.js.map +1 -1
  136. package/dist/gs/math/const.gs.d.ts +5 -5
  137. package/dist/gs/math/const.gs.js +4 -4
  138. package/dist/gs/math/const.gs.js.map +1 -1
  139. package/dist/gs/mime/index.d.ts +1 -0
  140. package/dist/gs/mime/index.js +50 -0
  141. package/dist/gs/mime/index.js.map +1 -0
  142. package/dist/gs/net/http/httptest/index.d.ts +11 -0
  143. package/dist/gs/net/http/httptest/index.js +21 -0
  144. package/dist/gs/net/http/httptest/index.js.map +1 -0
  145. package/dist/gs/net/http/index.d.ts +27 -0
  146. package/dist/gs/net/http/index.js +61 -0
  147. package/dist/gs/net/http/index.js.map +1 -0
  148. package/dist/gs/os/dir_unix.gs.js +2 -2
  149. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  150. package/dist/gs/os/types_js.gs.js.map +1 -1
  151. package/dist/gs/path/filepath/match.js +165 -3
  152. package/dist/gs/path/filepath/match.js.map +1 -1
  153. package/dist/gs/path/filepath/path.d.ts +3 -1
  154. package/dist/gs/path/filepath/path.js +133 -4
  155. package/dist/gs/path/filepath/path.js.map +1 -1
  156. package/dist/gs/path/path.d.ts +4 -1
  157. package/dist/gs/path/path.js +16 -4
  158. package/dist/gs/path/path.js.map +1 -1
  159. package/dist/gs/reflect/index.d.ts +1 -1
  160. package/dist/gs/reflect/index.js +1 -1
  161. package/dist/gs/reflect/index.js.map +1 -1
  162. package/dist/gs/reflect/map.js +3 -0
  163. package/dist/gs/reflect/map.js.map +1 -1
  164. package/dist/gs/reflect/type.d.ts +7 -4
  165. package/dist/gs/reflect/type.js +148 -7
  166. package/dist/gs/reflect/type.js.map +1 -1
  167. package/dist/gs/runtime/debug/index.d.ts +2 -0
  168. package/dist/gs/runtime/debug/index.js +8 -0
  169. package/dist/gs/runtime/debug/index.js.map +1 -0
  170. package/dist/gs/runtime/runtime.d.ts +35 -3
  171. package/dist/gs/runtime/runtime.js +72 -0
  172. package/dist/gs/runtime/runtime.js.map +1 -1
  173. package/dist/gs/slices/slices.d.ts +24 -5
  174. package/dist/gs/slices/slices.js +214 -5
  175. package/dist/gs/slices/slices.js.map +1 -1
  176. package/dist/gs/sort/slice.gs.d.ts +3 -3
  177. package/dist/gs/sort/slice.gs.js +6 -6
  178. package/dist/gs/sort/slice.gs.js.map +1 -1
  179. package/dist/gs/sort/sort.gs.d.ts +4 -4
  180. package/dist/gs/sort/sort.gs.js +11 -8
  181. package/dist/gs/sort/sort.gs.js.map +1 -1
  182. package/dist/gs/strings/builder.d.ts +1 -1
  183. package/dist/gs/strings/builder.js +3 -2
  184. package/dist/gs/strings/builder.js.map +1 -1
  185. package/dist/gs/sync/atomic/type.gs.d.ts +9 -8
  186. package/dist/gs/sync/atomic/type.gs.js +0 -2
  187. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  188. package/dist/gs/sync/sync.d.ts +2 -0
  189. package/dist/gs/sync/sync.js +27 -0
  190. package/dist/gs/sync/sync.js.map +1 -1
  191. package/dist/gs/syscall/constants.d.ts +36 -24
  192. package/dist/gs/syscall/constants.js +12 -0
  193. package/dist/gs/syscall/constants.js.map +1 -1
  194. package/dist/gs/syscall/errors.d.ts +2 -0
  195. package/dist/gs/syscall/errors.js +8 -0
  196. package/dist/gs/syscall/errors.js.map +1 -1
  197. package/dist/gs/syscall/fs.d.ts +43 -0
  198. package/dist/gs/syscall/fs.js +102 -0
  199. package/dist/gs/syscall/fs.js.map +1 -1
  200. package/dist/gs/syscall/js/index.d.ts +90 -0
  201. package/dist/gs/syscall/js/index.js +375 -0
  202. package/dist/gs/syscall/js/index.js.map +1 -0
  203. package/dist/gs/syscall/types.d.ts +22 -0
  204. package/dist/gs/syscall/types.js +45 -1
  205. package/dist/gs/syscall/types.js.map +1 -1
  206. package/dist/gs/testing/index.d.ts +1 -0
  207. package/dist/gs/testing/index.js +2 -0
  208. package/dist/gs/testing/index.js.map +1 -0
  209. package/dist/gs/testing/testing.d.ts +77 -0
  210. package/dist/gs/testing/testing.js +301 -0
  211. package/dist/gs/testing/testing.js.map +1 -0
  212. package/dist/gs/time/time.d.ts +41 -4
  213. package/dist/gs/time/time.js +205 -36
  214. package/dist/gs/time/time.js.map +1 -1
  215. package/dist/gs/unicode/unicode.d.ts +23 -1
  216. package/dist/gs/unicode/unicode.js +79 -10
  217. package/dist/gs/unicode/unicode.js.map +1 -1
  218. package/dist/gs/unicode/utf8/utf8.d.ts +4 -4
  219. package/dist/gs/unicode/utf8/utf8.js +24 -11
  220. package/dist/gs/unicode/utf8/utf8.js.map +1 -1
  221. package/dist/gs/unique/index.d.ts +11 -0
  222. package/dist/gs/unique/index.js +71 -0
  223. package/dist/gs/unique/index.js.map +1 -0
  224. package/go.sum +9 -0
  225. package/gs/builtin/builtin.ts +239 -8
  226. package/gs/builtin/channel.ts +22 -0
  227. package/gs/builtin/runtime-contract.test.ts +126 -0
  228. package/gs/builtin/slice.ts +259 -50
  229. package/gs/builtin/type.ts +109 -34
  230. package/gs/builtin/varRef.ts +38 -1
  231. package/gs/bytes/buffer.gs.ts +48 -44
  232. package/gs/bytes/meta.json +8 -3
  233. package/gs/bytes/reader.gs.ts +20 -19
  234. package/gs/context/context.test.ts +41 -0
  235. package/gs/context/context.ts +22 -26
  236. package/gs/crypto/internal/fips140deps/byteorder/index.ts +1 -0
  237. package/gs/crypto/internal/fips140deps/godebug/index.ts +1 -0
  238. package/gs/embed/index.ts +20 -0
  239. package/gs/embed/meta.json +5 -0
  240. package/gs/encoding/json/index.test.ts +15 -1
  241. package/gs/encoding/json/index.ts +24 -0
  242. package/gs/errors/errors.test.ts +82 -0
  243. package/gs/errors/errors.ts +104 -0
  244. package/gs/fmt/fmt.ts +56 -16
  245. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +73 -1
  246. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +297 -1
  247. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +159 -0
  248. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +1005 -0
  249. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +719 -0
  250. package/gs/github.com/aperturerobotics/starpc/srpc/meta.json +40 -0
  251. package/gs/github.com/klauspost/compress/internal/le/index.test.ts +36 -0
  252. package/gs/github.com/klauspost/compress/internal/le/index.ts +114 -0
  253. package/gs/go/internal/scannerhooks/index.test.ts +14 -0
  254. package/gs/go/internal/scannerhooks/index.ts +9 -0
  255. package/gs/go/scanner/index.test.ts +22 -0
  256. package/gs/go/scanner/index.ts +47 -0
  257. package/gs/go/token/index.test.ts +47 -1
  258. package/gs/go/token/index.ts +570 -4
  259. package/gs/internal/abi/index.test.ts +18 -0
  260. package/gs/internal/abi/index.ts +14 -0
  261. package/gs/internal/bytealg/index.test.ts +18 -0
  262. package/gs/internal/bytealg/index.ts +16 -0
  263. package/gs/internal/byteorder/index.test.ts +39 -0
  264. package/gs/internal/byteorder/index.ts +100 -27
  265. package/gs/internal/godebug/index.test.ts +16 -0
  266. package/gs/internal/godebug/index.ts +35 -0
  267. package/gs/io/fs/index.ts +1 -0
  268. package/gs/io/fs/meta.json +5 -0
  269. package/gs/io/fs/readlink.test.ts +43 -0
  270. package/gs/io/fs/readlink.ts +77 -0
  271. package/gs/io/fs/walk.test.ts +61 -0
  272. package/gs/io/fs/walk.ts +9 -9
  273. package/gs/io/io.ts +174 -31
  274. package/gs/io/meta.json +10 -2
  275. package/gs/maps/iter.ts +12 -6
  276. package/gs/maps/maps.ts +8 -6
  277. package/gs/math/bits/index.ts +103 -47
  278. package/gs/math/const.gs.test.ts +11 -5
  279. package/gs/math/const.gs.ts +5 -6
  280. package/gs/mime/index.ts +54 -0
  281. package/gs/net/http/httptest/index.ts +25 -0
  282. package/gs/net/http/index.test.ts +20 -0
  283. package/gs/net/http/index.ts +81 -0
  284. package/gs/os/dir_unix.gs.ts +2 -3
  285. package/gs/os/types_js.gs.ts +2 -2
  286. package/gs/path/filepath/match.test.ts +31 -12
  287. package/gs/path/filepath/match.ts +178 -3
  288. package/gs/path/filepath/path.test.ts +25 -0
  289. package/gs/path/filepath/path.ts +159 -5
  290. package/gs/path/path.ts +20 -5
  291. package/gs/reflect/index.ts +1 -0
  292. package/gs/reflect/map.test.ts +19 -0
  293. package/gs/reflect/map.ts +4 -0
  294. package/gs/reflect/type.ts +197 -17
  295. package/gs/runtime/debug/index.test.ts +24 -0
  296. package/gs/runtime/debug/index.ts +8 -0
  297. package/gs/runtime/runtime.test.ts +19 -0
  298. package/gs/runtime/runtime.ts +98 -3
  299. package/gs/slices/slices.test.ts +94 -0
  300. package/gs/slices/slices.ts +245 -5
  301. package/gs/sort/meta.json +7 -0
  302. package/gs/sort/slice.gs.ts +16 -7
  303. package/gs/sort/sort.gs.ts +16 -13
  304. package/gs/strings/builder.ts +4 -3
  305. package/gs/sync/atomic/type.gs.ts +13 -14
  306. package/gs/sync/meta.json +3 -1
  307. package/gs/sync/sync.test.ts +13 -1
  308. package/gs/sync/sync.ts +27 -0
  309. package/gs/syscall/constants.ts +39 -24
  310. package/gs/syscall/errors.ts +10 -0
  311. package/gs/syscall/fs.ts +195 -0
  312. package/gs/syscall/js/index.ts +458 -0
  313. package/gs/syscall/js/meta.json +4 -0
  314. package/gs/syscall/net.test.ts +85 -0
  315. package/gs/syscall/types.ts +56 -0
  316. package/gs/testing/index.ts +1 -0
  317. package/gs/testing/meta.json +5 -0
  318. package/gs/testing/testing.test.ts +90 -0
  319. package/gs/testing/testing.ts +382 -0
  320. package/gs/time/time.test.ts +106 -0
  321. package/gs/time/time.ts +278 -57
  322. package/gs/unicode/unicode.test.ts +25 -0
  323. package/gs/unicode/unicode.ts +119 -9
  324. package/gs/unicode/utf8/utf8.test.ts +13 -0
  325. package/gs/unicode/utf8/utf8.ts +28 -16
  326. package/gs/unique/index.ts +91 -0
  327. package/package.json +2 -1
@@ -0,0 +1,334 @@
1
+ package tsworkspace
2
+
3
+ import (
4
+ "bytes"
5
+ "context"
6
+ "os"
7
+ "os/exec"
8
+ "path/filepath"
9
+ "runtime"
10
+ "slices"
11
+ "strings"
12
+ "time"
13
+
14
+ jsoniter "github.com/aperturerobotics/json-iterator-lite"
15
+ "github.com/pkg/errors"
16
+ )
17
+
18
+ // Phase names a TypeScript workspace execution phase.
19
+ type Phase string
20
+
21
+ const (
22
+ // PhaseWorkspace covers workspace file preparation and tool discovery.
23
+ PhaseWorkspace Phase = "workspace"
24
+ // PhaseTypeCheck covers TypeScript type checking.
25
+ PhaseTypeCheck Phase = "typecheck"
26
+ // PhaseRuntime covers TypeScript runtime execution.
27
+ PhaseRuntime Phase = "runtime"
28
+
29
+ // NodeAmbientTypesFile names the GoScript runtime ambient declarations.
30
+ NodeAmbientTypesFile = "goscript-node.d.ts"
31
+ )
32
+
33
+ // Owner owns TypeScript test workspace files, tool discovery, and execution.
34
+ type Owner struct {
35
+ workDir string
36
+ toolDir string
37
+ }
38
+
39
+ // NewOwner creates a TypeScript workspace owner.
40
+ func NewOwner(workDir, toolDir string) *Owner {
41
+ return &Owner{
42
+ workDir: workDir,
43
+ toolDir: toolDir,
44
+ }
45
+ }
46
+
47
+ // WorkDir returns the workspace directory.
48
+ func (o *Owner) WorkDir() string {
49
+ if o == nil {
50
+ return ""
51
+ }
52
+ return o.workDir
53
+ }
54
+
55
+ // EnsurePackageJSON writes the module package metadata used by Bun.
56
+ func (o *Owner) EnsurePackageJSON() Result {
57
+ return o.WriteFile(PhaseWorkspace, "package.json", "{\"type\":\"module\"}\n")
58
+ }
59
+
60
+ // EnsureNodeAmbientTypes writes host runtime declarations needed by emitted tests.
61
+ func (o *Owner) EnsureNodeAmbientTypes() Result {
62
+ if NodeTypesPresent(o.workDir, o.toolDir) {
63
+ return o.WriteFile(PhaseWorkspace, NodeAmbientTypesFile, "")
64
+ }
65
+ return o.WriteFile(PhaseWorkspace, NodeAmbientTypesFile, nodeAmbientTypes)
66
+ }
67
+
68
+ // WriteFile writes a workspace-relative file.
69
+ func (o *Owner) WriteFile(phase Phase, name string, data string) Result {
70
+ if o == nil || o.workDir == "" {
71
+ return Result{Phase: phase, Error: "TypeScript workspace directory is empty"}
72
+ }
73
+ if err := os.MkdirAll(o.workDir, 0o755); err != nil {
74
+ return Result{Phase: phase, Error: errors.Wrap(err, "create TypeScript workspace").Error()}
75
+ }
76
+ path, err := o.workspacePath(name)
77
+ if err != nil {
78
+ return Result{Phase: phase, Error: err.Error()}
79
+ }
80
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
81
+ return Result{Phase: phase, Error: errors.Wrap(err, "create TypeScript workspace file parent").Error()}
82
+ }
83
+ if err := os.WriteFile(path, []byte(data), 0o644); err != nil {
84
+ return Result{Phase: phase, Error: errors.Wrap(err, "write TypeScript workspace file").Error()}
85
+ }
86
+ return Result{Phase: phase}
87
+ }
88
+
89
+ // WriteJSON writes an indented JSON workspace file.
90
+ func (o *Owner) WriteJSON(phase Phase, name string, value any) Result {
91
+ data, err := renderJSON(value)
92
+ if err != nil {
93
+ return Result{Phase: phase, Error: err.Error()}
94
+ }
95
+ return o.WriteFile(phase, name, data)
96
+ }
97
+
98
+ // RunTool finds and executes a TypeScript workspace tool.
99
+ func (o *Owner) RunTool(ctx context.Context, phase Phase, dir string, name string, args ...string) Result {
100
+ tool, err := o.FindTool(name)
101
+ if err != nil {
102
+ return Result{Phase: phase, Error: err.Error()}
103
+ }
104
+ if dir == "" {
105
+ dir = o.workDir
106
+ }
107
+ start := time.Now()
108
+ cmd := exec.CommandContext(ctx, tool, args...)
109
+ cmd.Dir = dir
110
+ var output bytes.Buffer
111
+ cmd.Stdout = &output
112
+ cmd.Stderr = &output
113
+ err = cmd.Run()
114
+ result := Result{
115
+ Phase: phase,
116
+ Command: append([]string{tool}, args...),
117
+ Output: output.String(),
118
+ Elapsed: time.Since(start),
119
+ }
120
+ if err != nil {
121
+ result.Error = err.Error()
122
+ }
123
+ return result
124
+ }
125
+
126
+ // FindTool locates a TypeScript workspace tool.
127
+ func (o *Owner) FindTool(name string) (string, error) {
128
+ if filepath.IsAbs(name) {
129
+ return name, nil
130
+ }
131
+ starts := []string{o.toolDir, o.workDir, sourceDirectory(), currentWorkingDirectory()}
132
+ for _, start := range starts {
133
+ if start == "" {
134
+ continue
135
+ }
136
+ for current := start; current != ""; current = filepath.Dir(current) {
137
+ candidate := filepath.Join(current, "node_modules", ".bin", name)
138
+ info, err := os.Stat(candidate)
139
+ if err == nil && !info.IsDir() {
140
+ return candidate, nil
141
+ }
142
+ if parent := filepath.Dir(current); parent == current {
143
+ break
144
+ }
145
+ }
146
+ }
147
+ if path, err := exec.LookPath(name); err == nil {
148
+ return path, nil
149
+ }
150
+ return "", errors.New(name + " not found in PATH or ancestor node_modules/.bin")
151
+ }
152
+
153
+ func (o *Owner) workspacePath(name string) (string, error) {
154
+ if filepath.IsAbs(name) {
155
+ return "", errors.Errorf("TypeScript workspace path must be relative: %s", name)
156
+ }
157
+ clean := filepath.Clean(name)
158
+ if clean == "." || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
159
+ return "", errors.Errorf("TypeScript workspace path escapes workspace: %s", name)
160
+ }
161
+ return filepath.Join(o.workDir, clean), nil
162
+ }
163
+
164
+ // NodeTypeRoots returns existing @types roots visible from the provided dirs.
165
+ func NodeTypeRoots(dirs ...string) []string {
166
+ seen := make(map[string]bool)
167
+ var roots []string
168
+ for _, start := range append(dirs, currentWorkingDirectory()) {
169
+ if start == "" {
170
+ continue
171
+ }
172
+ for current := start; current != ""; current = filepath.Dir(current) {
173
+ candidate := filepath.Join(current, "node_modules", "@types")
174
+ info, err := os.Stat(candidate)
175
+ if err == nil && info.IsDir() && !seen[candidate] {
176
+ seen[candidate] = true
177
+ roots = append(roots, candidate)
178
+ break
179
+ }
180
+ if parent := filepath.Dir(current); parent == current {
181
+ break
182
+ }
183
+ }
184
+ }
185
+ slices.Sort(roots)
186
+ return roots
187
+ }
188
+
189
+ // NodeTypesPresent reports whether @types/node is visible from any provided directory.
190
+ func NodeTypesPresent(dirs ...string) bool {
191
+ seen := make(map[string]bool)
192
+ for _, start := range dirs {
193
+ if start == "" {
194
+ continue
195
+ }
196
+ for current := start; current != ""; current = filepath.Dir(current) {
197
+ if seen[current] {
198
+ if parent := filepath.Dir(current); parent == current {
199
+ break
200
+ }
201
+ continue
202
+ }
203
+ seen[current] = true
204
+ candidate := filepath.Join(current, "node_modules", "@types", "node")
205
+ info, err := os.Stat(candidate)
206
+ if err == nil && info.IsDir() {
207
+ return true
208
+ }
209
+ if parent := filepath.Dir(current); parent == current {
210
+ break
211
+ }
212
+ }
213
+ }
214
+ return false
215
+ }
216
+
217
+ func currentWorkingDirectory() string {
218
+ wd, err := os.Getwd()
219
+ if err != nil {
220
+ return ""
221
+ }
222
+ return wd
223
+ }
224
+
225
+ func sourceDirectory() string {
226
+ _, file, _, ok := runtime.Caller(0)
227
+ if !ok {
228
+ return ""
229
+ }
230
+ return filepath.Dir(file)
231
+ }
232
+
233
+ const nodeAmbientTypes = `declare const process: {
234
+ env?: Record<string, string | undefined>
235
+ exit?: (code?: number) => never
236
+ }
237
+
238
+ declare var gc: (() => void) | undefined
239
+
240
+ declare namespace NodeJS {
241
+ type Timeout = ReturnType<typeof setTimeout>
242
+ }
243
+
244
+ declare module 'node:fs' {
245
+ export function mkdirSync(...args: unknown[]): unknown
246
+ }
247
+
248
+ declare module 'node:os' {
249
+ export function tmpdir(): string
250
+ }
251
+
252
+ declare module 'node:path' {
253
+ export function join(...parts: string[]): string
254
+ }
255
+ `
256
+
257
+ func renderJSON(value any) (string, error) {
258
+ stream := jsoniter.NewStream(nil, 512, 2)
259
+ if err := writeJSONValue(stream, value); err != nil {
260
+ return "", errors.Wrap(err, "marshal TypeScript workspace JSON")
261
+ }
262
+ stream.WriteRaw("\n")
263
+ if stream.Error != nil {
264
+ return "", errors.Wrap(stream.Error, "marshal TypeScript workspace JSON")
265
+ }
266
+ return string(stream.Buffer()), nil
267
+ }
268
+
269
+ func writeJSONValue(stream *jsoniter.Stream, value any) error {
270
+ switch typed := value.(type) {
271
+ case nil:
272
+ stream.WriteNil()
273
+ case bool:
274
+ stream.WriteBool(typed)
275
+ case string:
276
+ stream.WriteString(typed)
277
+ case []string:
278
+ stream.WriteArrayStart()
279
+ for idx, item := range typed {
280
+ if idx != 0 {
281
+ stream.WriteMore()
282
+ }
283
+ stream.WriteString(item)
284
+ }
285
+ stream.WriteArrayEnd()
286
+ case []any:
287
+ stream.WriteArrayStart()
288
+ for idx, item := range typed {
289
+ if idx != 0 {
290
+ stream.WriteMore()
291
+ }
292
+ if err := writeJSONValue(stream, item); err != nil {
293
+ return err
294
+ }
295
+ }
296
+ stream.WriteArrayEnd()
297
+ case map[string]string:
298
+ return writeJSONObject(stream, typed, func(value string) error {
299
+ stream.WriteString(value)
300
+ return nil
301
+ })
302
+ case map[string][]string:
303
+ return writeJSONObject(stream, typed, func(value []string) error {
304
+ return writeJSONValue(stream, value)
305
+ })
306
+ case map[string]any:
307
+ return writeJSONObject(stream, typed, func(value any) error {
308
+ return writeJSONValue(stream, value)
309
+ })
310
+ default:
311
+ return errors.Errorf("unsupported TypeScript workspace JSON value %T", value)
312
+ }
313
+ return nil
314
+ }
315
+
316
+ func writeJSONObject[T any](stream *jsoniter.Stream, fields map[string]T, writeValue func(T) error) error {
317
+ stream.WriteObjectStart()
318
+ keys := make([]string, 0, len(fields))
319
+ for key := range fields {
320
+ keys = append(keys, key)
321
+ }
322
+ slices.Sort(keys)
323
+ for idx, key := range keys {
324
+ if idx != 0 {
325
+ stream.WriteMore()
326
+ }
327
+ stream.WriteObjectField(key)
328
+ if err := writeValue(fields[key]); err != nil {
329
+ return err
330
+ }
331
+ }
332
+ stream.WriteObjectEnd()
333
+ return nil
334
+ }
@@ -0,0 +1,93 @@
1
+ package tsworkspace
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ func TestOwnerWritesWorkspaceFiles(t *testing.T) {
12
+ dir := t.TempDir()
13
+ owner := NewOwner(dir, dir)
14
+
15
+ if result := owner.EnsurePackageJSON(); result.Failed() {
16
+ t.Fatalf("write package json: %s", result.Error)
17
+ }
18
+ if result := owner.WriteJSON(PhaseWorkspace, "tsconfig.json", map[string]any{
19
+ "compilerOptions": map[string]any{
20
+ "strict": true,
21
+ },
22
+ }); result.Failed() {
23
+ t.Fatalf("write tsconfig: %s", result.Error)
24
+ }
25
+
26
+ for _, name := range []string{"package.json", "tsconfig.json"} {
27
+ if _, err := os.Stat(filepath.Join(dir, name)); err != nil {
28
+ t.Fatalf("expected %s: %v", name, err)
29
+ }
30
+ }
31
+ }
32
+
33
+ func TestOwnerRejectsWorkspacePathEscapes(t *testing.T) {
34
+ dir := t.TempDir()
35
+ owner := NewOwner(dir, dir)
36
+
37
+ for _, name := range []string{"../escape.ts", filepath.Join("nested", "..", "..", "escape.ts"), filepath.Join(dir, "escape.ts")} {
38
+ if result := owner.WriteFile(PhaseWorkspace, name, "escape"); !result.Failed() {
39
+ t.Fatalf("expected %s to fail", name)
40
+ }
41
+ }
42
+ }
43
+
44
+ func TestOwnerRunsToolsWithCapturedOutput(t *testing.T) {
45
+ dir := t.TempDir()
46
+ owner := NewOwner(dir, dir)
47
+
48
+ result := owner.RunTool(context.Background(), PhaseRuntime, dir, "go", "env", "GOVERSION")
49
+ if result.Failed() {
50
+ t.Fatalf("run tool: %s\n%s", result.Error, result.Output)
51
+ }
52
+ if result.Output == "" {
53
+ t.Fatalf("expected captured output")
54
+ }
55
+ if len(result.Command) == 0 {
56
+ t.Fatalf("expected command capture")
57
+ }
58
+ }
59
+
60
+ func TestOwnerWritesNodeAmbientTypes(t *testing.T) {
61
+ dir := t.TempDir()
62
+ owner := NewOwner(dir, dir)
63
+
64
+ if result := owner.EnsureNodeAmbientTypes(); result.Failed() {
65
+ t.Fatalf("write node ambient types: %s", result.Error)
66
+ }
67
+ data, err := os.ReadFile(filepath.Join(dir, NodeAmbientTypesFile))
68
+ if err != nil {
69
+ t.Fatalf("read node ambient types: %v", err)
70
+ }
71
+ if !strings.Contains(string(data), "declare module 'node:fs'") {
72
+ t.Fatalf("node ambient types missing node:fs declaration: %s", data)
73
+ }
74
+ }
75
+
76
+ func TestOwnerSkipsNodeAmbientTypesWhenNodeTypesExist(t *testing.T) {
77
+ dir := t.TempDir()
78
+ if err := os.MkdirAll(filepath.Join(dir, "node_modules", "@types", "node"), 0o755); err != nil {
79
+ t.Fatal(err.Error())
80
+ }
81
+ owner := NewOwner(filepath.Join(dir, "work"), dir)
82
+
83
+ if result := owner.EnsureNodeAmbientTypes(); result.Failed() {
84
+ t.Fatalf("write node ambient types: %s", result.Error)
85
+ }
86
+ data, err := os.ReadFile(filepath.Join(dir, "work", NodeAmbientTypesFile))
87
+ if err != nil {
88
+ t.Fatalf("read node ambient types: %v", err)
89
+ }
90
+ if strings.TrimSpace(string(data)) != "" {
91
+ t.Fatalf("node ambient types should be empty when @types/node is already visible: %s", data)
92
+ }
93
+ }
@@ -0,0 +1,17 @@
1
+ package tsworkspace
2
+
3
+ import "time"
4
+
5
+ // Result describes one workspace file or process operation.
6
+ type Result struct {
7
+ Phase Phase
8
+ Command []string
9
+ Output string
10
+ Error string
11
+ Elapsed time.Duration
12
+ }
13
+
14
+ // Failed returns true when the operation failed.
15
+ func (r Result) Failed() bool {
16
+ return r.Error != ""
17
+ }