goscript 0.0.84 → 0.1.0

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 (188) hide show
  1. package/README.md +13 -1
  2. package/cmd/goscript/cmd_compile.go +70 -69
  3. package/cmd/goscript/cmd_compile_test.go +79 -0
  4. package/cmd/goscript/main.go +10 -5
  5. package/compiler/compile-request.go +218 -0
  6. package/compiler/compiler.go +16 -1336
  7. package/compiler/compliance_test.go +196 -0
  8. package/compiler/config.go +6 -13
  9. package/compiler/diagnostic.go +70 -0
  10. package/compiler/index.test.ts +28 -28
  11. package/compiler/index.ts +40 -72
  12. package/compiler/lowered-program.go +132 -0
  13. package/compiler/lowering.go +3576 -0
  14. package/compiler/override-registry.go +422 -0
  15. package/compiler/override-registry_test.go +207 -0
  16. package/compiler/package-graph.go +231 -0
  17. package/compiler/package-graph_test.go +281 -0
  18. package/compiler/result.go +13 -0
  19. package/compiler/runtime-contract.go +279 -0
  20. package/compiler/runtime-contract_test.go +90 -0
  21. package/compiler/semantic-model-types.go +110 -0
  22. package/compiler/semantic-model.go +922 -0
  23. package/compiler/semantic-model_test.go +416 -0
  24. package/compiler/service.go +133 -0
  25. package/compiler/skeleton_test.go +1145 -0
  26. package/compiler/typescript-emitter.go +663 -0
  27. package/compiler/wasm/compile.go +2 -3
  28. package/compiler/wasm/compile_test.go +29 -0
  29. package/compiler/wasm_api.go +10 -159
  30. package/dist/compiler/index.d.ts +1 -3
  31. package/dist/compiler/index.js +31 -55
  32. package/dist/compiler/index.js.map +1 -1
  33. package/dist/gs/builtin/builtin.d.ts +13 -0
  34. package/dist/gs/builtin/builtin.js +23 -0
  35. package/dist/gs/builtin/builtin.js.map +1 -1
  36. package/dist/gs/builtin/channel.d.ts +3 -3
  37. package/dist/gs/builtin/channel.js.map +1 -1
  38. package/dist/gs/builtin/hostio.d.ts +15 -1
  39. package/dist/gs/builtin/hostio.js +134 -49
  40. package/dist/gs/builtin/hostio.js.map +1 -1
  41. package/dist/gs/builtin/index.d.ts +1 -0
  42. package/dist/gs/builtin/index.js +1 -0
  43. package/dist/gs/builtin/index.js.map +1 -1
  44. package/dist/gs/builtin/slice.d.ts +1 -1
  45. package/dist/gs/builtin/slice.js.map +1 -1
  46. package/dist/gs/builtin/type.d.ts +11 -0
  47. package/dist/gs/builtin/type.js +55 -1
  48. package/dist/gs/builtin/type.js.map +1 -1
  49. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  50. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  51. package/dist/gs/bytes/reader.gs.js.map +1 -1
  52. package/dist/gs/context/context.js.map +1 -1
  53. package/dist/gs/crypto/rand/index.d.ts +5 -0
  54. package/dist/gs/crypto/rand/index.js +77 -0
  55. package/dist/gs/crypto/rand/index.js.map +1 -0
  56. package/dist/gs/encoding/json/index.d.ts +3 -0
  57. package/dist/gs/encoding/json/index.js +160 -0
  58. package/dist/gs/encoding/json/index.js.map +1 -0
  59. package/dist/gs/fmt/fmt.js.map +1 -1
  60. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -1
  61. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +1 -1
  62. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  63. package/dist/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/browser.js.map +1 -1
  64. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  65. package/dist/gs/github.com/pkg/errors/stack.js.map +1 -1
  66. package/dist/gs/go/scanner/index.d.ts +29 -0
  67. package/dist/gs/go/scanner/index.js +120 -0
  68. package/dist/gs/go/scanner/index.js.map +1 -0
  69. package/dist/gs/go/token/index.d.ts +31 -0
  70. package/dist/gs/go/token/index.js +82 -0
  71. package/dist/gs/go/token/index.js.map +1 -0
  72. package/dist/gs/internal/abi/index.js.map +1 -1
  73. package/dist/gs/io/fs/fs.js.map +1 -1
  74. package/dist/gs/io/fs/readdir.js.map +1 -1
  75. package/dist/gs/io/fs/readfile.js.map +1 -1
  76. package/dist/gs/io/fs/stat.js.map +1 -1
  77. package/dist/gs/io/fs/sub.js.map +1 -1
  78. package/dist/gs/io/io.js.map +1 -1
  79. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  80. package/dist/gs/os/error.gs.js +2 -4
  81. package/dist/gs/os/error.gs.js.map +1 -1
  82. package/dist/gs/os/exec.gs.js.map +1 -1
  83. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  84. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  85. package/dist/gs/os/root_js.gs.js.map +1 -1
  86. package/dist/gs/os/tempfile.gs.js +66 -9
  87. package/dist/gs/os/tempfile.gs.js.map +1 -1
  88. package/dist/gs/os/types.gs.js.map +1 -1
  89. package/dist/gs/os/types_js.gs.js +9 -9
  90. package/dist/gs/os/types_js.gs.js.map +1 -1
  91. package/dist/gs/os/types_unix.gs.js.map +1 -1
  92. package/dist/gs/path/filepath/match.js.map +1 -1
  93. package/dist/gs/path/match.js.map +1 -1
  94. package/dist/gs/path/path.js.map +1 -1
  95. package/dist/gs/reflect/index.d.ts +2 -2
  96. package/dist/gs/reflect/index.js +1 -1
  97. package/dist/gs/reflect/index.js.map +1 -1
  98. package/dist/gs/reflect/map.js.map +1 -1
  99. package/dist/gs/reflect/type.d.ts +2 -1
  100. package/dist/gs/reflect/type.js +85 -14
  101. package/dist/gs/reflect/type.js.map +1 -1
  102. package/dist/gs/reflect/types.js.map +1 -1
  103. package/dist/gs/reflect/visiblefields.js.map +1 -1
  104. package/dist/gs/runtime/runtime.js.map +1 -1
  105. package/dist/gs/sort/sort.gs.js.map +1 -1
  106. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  107. package/dist/gs/strconv/quote.gs.js.map +1 -1
  108. package/dist/gs/strings/builder.js.map +1 -1
  109. package/dist/gs/strings/reader.js.map +1 -1
  110. package/dist/gs/strings/replace.js.map +1 -1
  111. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  112. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  113. package/dist/gs/sync/sync.d.ts +1 -0
  114. package/dist/gs/sync/sync.js +12 -0
  115. package/dist/gs/sync/sync.js.map +1 -1
  116. package/dist/gs/time/time.js.map +1 -1
  117. package/dist/gs/unicode/unicode.js.map +1 -1
  118. package/go.mod +2 -2
  119. package/gs/builtin/builtin.ts +27 -0
  120. package/gs/builtin/hostio.test.ts +177 -0
  121. package/gs/builtin/hostio.ts +171 -56
  122. package/gs/builtin/index.ts +1 -0
  123. package/gs/builtin/runtime-contract.test.ts +230 -0
  124. package/gs/builtin/type.ts +84 -1
  125. package/gs/crypto/rand/index.test.ts +32 -0
  126. package/gs/crypto/rand/index.ts +90 -0
  127. package/gs/crypto/rand/meta.json +5 -0
  128. package/gs/encoding/json/index.test.ts +65 -0
  129. package/gs/encoding/json/index.ts +186 -0
  130. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +23 -0
  131. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +3 -1
  132. package/gs/github.com/aperturerobotics/wasivm/wazero/kernel/runtime/browser/meta.json +3 -1
  133. package/gs/go/scanner/index.test.ts +50 -0
  134. package/gs/go/scanner/index.ts +157 -0
  135. package/gs/go/token/index.test.ts +21 -0
  136. package/gs/go/token/index.ts +120 -0
  137. package/gs/os/file_unix_js.test.ts +50 -0
  138. package/gs/os/meta.json +1 -2
  139. package/gs/os/tempfile.gs.test.ts +85 -0
  140. package/gs/os/tempfile.gs.ts +71 -11
  141. package/gs/os/types_js.gs.ts +9 -9
  142. package/gs/reflect/index.ts +1 -1
  143. package/gs/reflect/type.ts +106 -17
  144. package/gs/reflect/typefor.test.ts +75 -0
  145. package/gs/sync/sync.test.ts +24 -0
  146. package/gs/sync/sync.ts +12 -0
  147. package/package.json +13 -13
  148. package/compiler/analysis.go +0 -3475
  149. package/compiler/analysis_test.go +0 -338
  150. package/compiler/assignment.go +0 -580
  151. package/compiler/builtin_test.go +0 -92
  152. package/compiler/code-writer.go +0 -115
  153. package/compiler/compiler_test.go +0 -149
  154. package/compiler/composite-lit.go +0 -779
  155. package/compiler/config_test.go +0 -62
  156. package/compiler/constraint.go +0 -86
  157. package/compiler/decl.go +0 -801
  158. package/compiler/expr-call-async.go +0 -188
  159. package/compiler/expr-call-builtins.go +0 -208
  160. package/compiler/expr-call-helpers.go +0 -382
  161. package/compiler/expr-call-make.go +0 -318
  162. package/compiler/expr-call-type-conversion.go +0 -520
  163. package/compiler/expr-call.go +0 -413
  164. package/compiler/expr-selector.go +0 -343
  165. package/compiler/expr-star.go +0 -82
  166. package/compiler/expr-type.go +0 -442
  167. package/compiler/expr-value.go +0 -89
  168. package/compiler/expr.go +0 -773
  169. package/compiler/field.go +0 -183
  170. package/compiler/gs_dependencies_test.go +0 -298
  171. package/compiler/lit.go +0 -322
  172. package/compiler/output.go +0 -72
  173. package/compiler/primitive.go +0 -149
  174. package/compiler/protobuf.go +0 -697
  175. package/compiler/sanitize.go +0 -100
  176. package/compiler/spec-struct.go +0 -995
  177. package/compiler/spec-value.go +0 -540
  178. package/compiler/spec.go +0 -725
  179. package/compiler/stmt-assign.go +0 -664
  180. package/compiler/stmt-for.go +0 -266
  181. package/compiler/stmt-range.go +0 -475
  182. package/compiler/stmt-select.go +0 -262
  183. package/compiler/stmt-type-switch.go +0 -147
  184. package/compiler/stmt.go +0 -1308
  185. package/compiler/type-assert.go +0 -386
  186. package/compiler/type-info.go +0 -156
  187. package/compiler/type-utils.go +0 -207
  188. package/compiler/type.go +0 -892
package/go.mod CHANGED
@@ -6,17 +6,17 @@ toolchain go1.26.2
6
6
 
7
7
  require (
8
8
  github.com/aperturerobotics/cli v1.1.0
9
+ github.com/aperturerobotics/json-iterator-lite v1.0.1-0.20251104042408-0c9eb8a3f726
9
10
  github.com/aperturerobotics/protobuf-go-lite v0.12.2
10
11
  github.com/aperturerobotics/util v1.33.0
11
12
  github.com/pkg/errors v0.9.1
12
13
  github.com/sirupsen/logrus v1.9.5-0.20260309202648-9f0600962f75
13
- golang.org/x/mod v0.34.0
14
14
  golang.org/x/tools v0.43.0
15
15
  )
16
16
 
17
17
  require (
18
- github.com/aperturerobotics/json-iterator-lite v1.0.1-0.20251104042408-0c9eb8a3f726 // indirect
19
18
  github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
19
+ golang.org/x/mod v0.34.0 // indirect
20
20
  golang.org/x/sync v0.20.0 // indirect
21
21
  golang.org/x/sys v0.42.0 // indirect
22
22
  )
@@ -2,6 +2,15 @@ import type { Slice, SliceProxy } from './slice.js'
2
2
  import { writeHostStdoutText } from './hostio.js'
3
3
  import { formatPrintedArgs } from './print.js'
4
4
  import { isSliceProxy } from './slice.js'
5
+ import { isVarRef, type VarRef } from './varRef.js'
6
+
7
+ /**
8
+ * Implementation of Go's built-in print function
9
+ * @param args Arguments to print
10
+ */
11
+ export function print(...args: any[]): void {
12
+ writeHostStdoutText(args.length === 0 ? '' : formatPrintedArgs(args))
13
+ }
5
14
 
6
15
  /**
7
16
  * Implementation of Go's built-in println function
@@ -69,6 +78,24 @@ export function assignStruct<T>(target: T, source: T): void {
69
78
  }
70
79
  }
71
80
 
81
+ /**
82
+ * pointerValue unwraps a Go pointer value for generated field, method, and
83
+ * dereference access. Struct pointers may be represented directly as class
84
+ * instances or indirectly as VarRef values when the pointer came from taking a
85
+ * variable's address.
86
+ */
87
+ export function pointerValue<T>(value: T | VarRef<T> | null | undefined): T {
88
+ if (value === null || value === undefined) {
89
+ throw new Error(
90
+ 'runtime error: invalid memory address or nil pointer dereference',
91
+ )
92
+ }
93
+ if (isVarRef(value)) {
94
+ return value.value as T
95
+ }
96
+ return value
97
+ }
98
+
72
99
  // Bytes represents all valid []byte representations in TypeScript
73
100
  // This includes Uint8Array (the preferred representation) and $.Slice<number> (which includes null)
74
101
  export type Bytes = Uint8Array | Slice<number>
@@ -1,6 +1,10 @@
1
1
  import { afterEach, describe, expect, it, vi } from 'vitest'
2
2
 
3
3
  import {
4
+ getHostRuntime,
5
+ HostRuntimeOwner,
6
+ type HostRuntime,
7
+ isMainScript,
4
8
  resetHostRuntimeForTests,
5
9
  writeHostStderrText,
6
10
  writeHostStdoutText,
@@ -10,6 +14,8 @@ const originalDeno = (globalThis as any).Deno
10
14
  const originalProcess = (globalThis as any).process
11
15
 
12
16
  afterEach(() => {
17
+ vi.restoreAllMocks()
18
+
13
19
  if (originalDeno === undefined) {
14
20
  delete (globalThis as any).Deno
15
21
  } else {
@@ -25,6 +31,40 @@ afterEach(() => {
25
31
  resetHostRuntimeForTests()
26
32
  })
27
33
 
34
+ function runtimeFixture(platform: string): HostRuntime {
35
+ return {
36
+ deno: null,
37
+ getEnv: () => '',
38
+ getStdioHandle: () => null,
39
+ nodeFS: null,
40
+ platform,
41
+ processObj: null,
42
+ readFD: () => null,
43
+ writeFD: () => 0,
44
+ writeStderrText: () => {},
45
+ writeStdoutText: () => {},
46
+ }
47
+ }
48
+
49
+ describe('hostio runtime owner', () => {
50
+ it('caches host capability detection until reset', () => {
51
+ let detects = 0
52
+ const owner = new HostRuntimeOwner(() => {
53
+ detects++
54
+ return runtimeFixture(`host-${detects}`)
55
+ })
56
+
57
+ expect(owner.current().platform).toBe('host-1')
58
+ expect(owner.current().platform).toBe('host-1')
59
+ expect(detects).toBe(1)
60
+
61
+ owner.reset()
62
+
63
+ expect(owner.current().platform).toBe('host-2')
64
+ expect(detects).toBe(2)
65
+ })
66
+ })
67
+
28
68
  describe('hostio text writes', () => {
29
69
  it('uses sync node fs writes for stdout and stderr', () => {
30
70
  const writes: Array<{ fd: number; bytes: number[] }> = []
@@ -66,4 +106,141 @@ describe('hostio text writes', () => {
66
106
  expect((globalThis as any).process.stdout.write).not.toHaveBeenCalled()
67
107
  expect((globalThis as any).process.stderr.write).not.toHaveBeenCalled()
68
108
  })
109
+
110
+ it('falls back to node fs when Deno is present without sync stdio', () => {
111
+ const writes: Array<{ fd: number; bytes: number[] }> = []
112
+ const writeSync = vi.fn(
113
+ (
114
+ fd: number,
115
+ buffer: Uint8Array,
116
+ _offset?: number,
117
+ length?: number,
118
+ _position?: number | null,
119
+ ) => {
120
+ writes.push({
121
+ bytes: Array.from(buffer.subarray(0, length ?? buffer.length)),
122
+ fd,
123
+ })
124
+ return length ?? buffer.length
125
+ },
126
+ )
127
+
128
+ ;(globalThis as any).Deno = {
129
+ stderr: {},
130
+ stdout: {},
131
+ }
132
+ ;(globalThis as any).process = {
133
+ getBuiltinModule: vi.fn(() => ({
134
+ readSync: vi.fn(),
135
+ writeSync,
136
+ })),
137
+ }
138
+ resetHostRuntimeForTests()
139
+
140
+ writeHostStdoutText('bun\n')
141
+ writeHostStderrText('err\n')
142
+
143
+ expect(writeSync).toHaveBeenCalledTimes(2)
144
+ expect(writes).toEqual([
145
+ { bytes: [98, 117, 110, 10], fd: 1 },
146
+ { bytes: [101, 114, 114, 10], fd: 2 },
147
+ ])
148
+ expect(getHostRuntime().platform).toBe('unknown')
149
+ })
150
+
151
+ it('prefers Deno sync stdio when it is available', () => {
152
+ const denoWrites: Array<{ bytes: number[]; stream: string }> = []
153
+ const nodeWriteSync = vi.fn()
154
+ const denoWriteSync = vi.fn((buffer: Uint8Array) => {
155
+ denoWrites.push({
156
+ bytes: Array.from(buffer),
157
+ stream: 'stdout',
158
+ })
159
+ return buffer.length
160
+ })
161
+
162
+ ;(globalThis as any).Deno = {
163
+ build: { os: 'darwin' },
164
+ stdout: { writeSync: denoWriteSync },
165
+ }
166
+ ;(globalThis as any).process = {
167
+ getBuiltinModule: vi.fn(() => ({
168
+ readSync: vi.fn(),
169
+ writeSync: nodeWriteSync,
170
+ })),
171
+ }
172
+ resetHostRuntimeForTests()
173
+
174
+ writeHostStdoutText('deno\n')
175
+
176
+ expect(denoWriteSync).toHaveBeenCalledTimes(1)
177
+ expect(nodeWriteSync).not.toHaveBeenCalled()
178
+ expect(denoWrites).toEqual([
179
+ { bytes: [100, 101, 110, 111, 10], stream: 'stdout' },
180
+ ])
181
+ expect(getHostRuntime().platform).toBe('darwin')
182
+ })
183
+
184
+ it('uses console fallback in browser-like hosts', () => {
185
+ const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
186
+
187
+ delete (globalThis as any).Deno
188
+ delete (globalThis as any).process
189
+ resetHostRuntimeForTests()
190
+
191
+ writeHostStdoutText('browser\n')
192
+
193
+ expect(getHostRuntime().platform).toBe('unknown')
194
+ expect(getHostRuntime().nodeFS).toBeNull()
195
+ expect(consoleLog).toHaveBeenCalledWith('browser')
196
+ })
197
+ })
198
+
199
+ describe('hostio isMainScript', () => {
200
+ it('returns true when the runtime marks the module as main', () => {
201
+ expect(
202
+ isMainScript({
203
+ main: true,
204
+ url: 'file:///tmp/example.gs.ts',
205
+ }),
206
+ ).toBe(true)
207
+ })
208
+
209
+ it('matches process argv[1] against the module url', () => {
210
+ delete (globalThis as any).Deno
211
+ ;(globalThis as any).process = {
212
+ argv: ['bun', './dist/app.gs.ts'],
213
+ cwd: vi.fn(() => '/tmp/project'),
214
+ getBuiltinModule: vi.fn(() => ({
215
+ readSync: vi.fn(),
216
+ writeSync: vi.fn(),
217
+ })),
218
+ }
219
+ resetHostRuntimeForTests()
220
+
221
+ expect(
222
+ isMainScript({
223
+ url: 'file:///tmp/project/dist/app.gs.ts',
224
+ }),
225
+ ).toBe(true)
226
+ })
227
+
228
+ it('returns false when the module was imported instead of executed', () => {
229
+ delete (globalThis as any).Deno
230
+ ;(globalThis as any).process = {
231
+ argv: ['bun', './runner.ts'],
232
+ cwd: vi.fn(() => '/tmp/project'),
233
+ getBuiltinModule: vi.fn(() => ({
234
+ readSync: vi.fn(),
235
+ writeSync: vi.fn(),
236
+ })),
237
+ }
238
+ resetHostRuntimeForTests()
239
+
240
+ expect(
241
+ isMainScript({
242
+ url: 'file:///tmp/project/dist/app.gs.ts',
243
+ }),
244
+ ).toBe(false)
245
+ })
69
246
  })
@@ -29,19 +29,29 @@ export type NodeFSModule = {
29
29
  lchownSync?(path: string, uid: number, gid: number): void
30
30
  linkSync?(existingPath: string, newPath: string): void
31
31
  lstatSync?(path: string): any
32
- mkdirSync?(path: string, options?: number | { mode?: number; recursive?: boolean }): void
32
+ mkdirSync?(
33
+ path: string,
34
+ options?: number | { mode?: number; recursive?: boolean },
35
+ ): void
33
36
  readFileSync?(path: string): Uint8Array
34
37
  readdirSync?(path: string, options?: { withFileTypes?: boolean }): any[]
35
38
  readlinkSync?(path: string): string
36
39
  renameSync?(oldPath: string, newPath: string): void
37
- rmSync?(path: string, options?: { force?: boolean; recursive?: boolean }): void
40
+ rmSync?(
41
+ path: string,
42
+ options?: { force?: boolean; recursive?: boolean },
43
+ ): void
38
44
  rmdirSync?(path: string): void
39
45
  statSync?(path: string): any
40
46
  symlinkSync?(target: string, path: string): void
41
47
  truncateSync?(path: string, len?: number): void
42
48
  unlinkSync?(path: string): void
43
49
  utimesSync?(path: string, atime: Date | number, mtime: Date | number): void
44
- writeFileSync?(path: string, data: Uint8Array, options?: { mode?: number }): void
50
+ writeFileSync?(
51
+ path: string,
52
+ data: Uint8Array,
53
+ options?: { mode?: number },
54
+ ): void
45
55
  }
46
56
 
47
57
  export type DenoStream = {
@@ -75,6 +85,13 @@ export type HostRuntime = {
75
85
  writeStdoutText: HostTextWrite
76
86
  }
77
87
 
88
+ export type HostRuntimeDetector = () => HostRuntime
89
+
90
+ export type MainScriptMeta = {
91
+ url: string
92
+ main?: boolean
93
+ }
94
+
78
95
  const encoder = new TextEncoder()
79
96
 
80
97
  function getDynamicRequire(): ((specifier: string) => unknown) | null {
@@ -149,12 +166,66 @@ function detectNodeFS(processObj: any | null): NodeFSModule | null {
149
166
  return null
150
167
  }
151
168
 
152
- function unsupportedReadFD(_fd: number, _buffer: Uint8Array): number | null {
153
- throw new HostUnsupportedError()
169
+ function hasURLScheme(path: string): boolean {
170
+ return /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(path)
171
+ }
172
+
173
+ function isAbsoluteScriptPath(path: string): boolean {
174
+ return path.startsWith('/') || /^[A-Za-z]:[\\/]/.test(path)
175
+ }
176
+
177
+ function normalizePath(path: string): string {
178
+ return path.replace(/\\/g, '/')
179
+ }
180
+
181
+ function absolutePathToFileURL(path: string, isDirectory: boolean): string {
182
+ const normalized = normalizePath(path)
183
+ const suffix = isDirectory && !normalized.endsWith('/') ? '/' : ''
184
+ if (/^[A-Za-z]:\//.test(normalized)) {
185
+ return new URL(`file:///${normalized}${suffix}`).href
186
+ }
187
+ return new URL(`file://${normalized}${suffix}`).href
188
+ }
189
+
190
+ function getCurrentWorkingDirectory(): string | null {
191
+ const runtime = getHostRuntime()
192
+ try {
193
+ if (typeof runtime.processObj?.cwd === 'function') {
194
+ return runtime.processObj.cwd()
195
+ }
196
+ } catch {
197
+ // Fall through to Deno cwd.
198
+ }
199
+
200
+ try {
201
+ if (typeof runtime.deno?.cwd === 'function') {
202
+ return runtime.deno.cwd()
203
+ }
204
+ } catch {
205
+ // No cwd fallback available.
206
+ }
207
+
208
+ return null
154
209
  }
155
210
 
156
- function unsupportedWriteFD(_fd: number, _buffer: Uint8Array): number {
157
- throw new HostUnsupportedError()
211
+ function fileURLFromScriptPath(path: string): string | null {
212
+ try {
213
+ if (hasURLScheme(path)) {
214
+ return new URL(path).href
215
+ }
216
+
217
+ if (isAbsoluteScriptPath(path)) {
218
+ return absolutePathToFileURL(path, false)
219
+ }
220
+
221
+ const cwd = getCurrentWorkingDirectory()
222
+ if (!cwd) {
223
+ return null
224
+ }
225
+ return new URL(normalizePath(path), absolutePathToFileURL(cwd, true)).href
226
+ } catch {
227
+ return null
228
+ }
158
229
  }
159
230
 
160
231
  function fallbackConsoleWriter(method: 'error' | 'log'): HostTextWrite {
@@ -193,10 +264,7 @@ function detectHostRuntime(): HostRuntime {
193
264
  }
194
265
  }
195
266
 
196
- const platform =
197
- deno?.build?.os ??
198
- processObj?.platform ??
199
- 'unknown'
267
+ const platform = deno?.build?.os ?? processObj?.platform ?? 'unknown'
200
268
 
201
269
  const getEnv = (name: string): string => {
202
270
  if (deno?.env?.get) {
@@ -209,64 +277,67 @@ function detectHostRuntime(): HostRuntime {
209
277
  return processObj?.env?.[name] ?? ''
210
278
  }
211
279
 
212
- let readFD: HostReadFD = unsupportedReadFD
213
- let writeFD: HostWriteFD = unsupportedWriteFD
214
- let writeStdoutText: HostTextWrite = fallbackConsoleWriter('log')
215
- let writeStderrText: HostTextWrite = fallbackConsoleWriter('error')
216
-
217
- if (deno) {
218
- readFD = (fd: number, buffer: Uint8Array): number | null => {
219
- const handle = getStdioHandle(fd)
220
- if (!handle || typeof handle.readSync !== 'function') {
221
- throw new HostUnsupportedError()
222
- }
280
+ const readFD: HostReadFD = (
281
+ fd: number,
282
+ buffer: Uint8Array,
283
+ ): number | null => {
284
+ const handle = getStdioHandle(fd)
285
+ if (handle && typeof handle.readSync === 'function') {
223
286
  return handle.readSync(buffer)
224
287
  }
225
- writeFD = (fd: number, buffer: Uint8Array): number => {
226
- const handle = getStdioHandle(fd)
227
- if (!handle || typeof handle.writeSync !== 'function') {
228
- throw new HostUnsupportedError()
229
- }
288
+ if (nodeFS) {
289
+ return nodeFS.readSync(fd, buffer, 0, buffer.length, null)
290
+ }
291
+ throw new HostUnsupportedError()
292
+ }
293
+ const writeFD: HostWriteFD = (fd: number, buffer: Uint8Array): number => {
294
+ const handle = getStdioHandle(fd)
295
+ if (handle && typeof handle.writeSync === 'function') {
230
296
  return writeAllSync(
231
297
  (chunk: Uint8Array) => handle.writeSync!(chunk),
232
298
  buffer,
233
299
  )
234
300
  }
235
- writeStdoutText = (data: string) => {
236
- const handle = getStdioHandle(1)
237
- if (!handle || typeof handle.writeSync !== 'function') {
238
- fallbackConsoleWriter('log')(data)
239
- return
240
- }
241
- writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
242
- }
243
- writeStderrText = (data: string) => {
244
- const handle = getStdioHandle(2)
245
- if (!handle || typeof handle.writeSync !== 'function') {
246
- fallbackConsoleWriter('error')(data)
247
- return
248
- }
249
- writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
250
- }
251
- } else if (nodeFS) {
252
- readFD = (fd: number, buffer: Uint8Array): number | null =>
253
- nodeFS.readSync(fd, buffer, 0, buffer.length, null)
254
- writeFD = (fd: number, buffer: Uint8Array): number =>
255
- writeAllSync(
301
+ if (nodeFS) {
302
+ return writeAllSync(
256
303
  (chunk: Uint8Array) =>
257
304
  nodeFS.writeSync(fd, chunk, 0, chunk.length, null),
258
305
  buffer,
259
306
  )
260
- writeStdoutText = (data: string) =>
307
+ }
308
+ throw new HostUnsupportedError()
309
+ }
310
+ const writeStdoutText: HostTextWrite = (data: string) => {
311
+ const handle = getStdioHandle(1)
312
+ if (handle && typeof handle.writeSync === 'function') {
313
+ writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
314
+ return
315
+ }
316
+ if (nodeFS) {
261
317
  writeAllText(
262
- (chunk: Uint8Array) => nodeFS.writeSync(1, chunk, 0, chunk.length, null),
318
+ (chunk: Uint8Array) =>
319
+ nodeFS.writeSync(1, chunk, 0, chunk.length, null),
263
320
  data,
264
321
  )
265
- writeStderrText = (data: string) =>
322
+ return
323
+ }
324
+ fallbackConsoleWriter('log')(data)
325
+ }
326
+ const writeStderrText: HostTextWrite = (data: string) => {
327
+ const handle = getStdioHandle(2)
328
+ if (handle && typeof handle.writeSync === 'function') {
329
+ writeAllText((chunk: Uint8Array) => handle.writeSync!(chunk), data)
330
+ return
331
+ }
332
+ if (nodeFS) {
266
333
  writeAllText(
267
- (chunk: Uint8Array) => nodeFS.writeSync(2, chunk, 0, chunk.length, null),
334
+ (chunk: Uint8Array) =>
335
+ nodeFS.writeSync(2, chunk, 0, chunk.length, null),
268
336
  data,
269
337
  )
338
+ return
339
+ }
340
+ fallbackConsoleWriter('error')(data)
270
341
  }
271
342
 
272
343
  return {
@@ -283,16 +354,60 @@ function detectHostRuntime(): HostRuntime {
283
354
  }
284
355
  }
285
356
 
286
- export let hostRuntime = detectHostRuntime()
357
+ export class HostRuntimeOwner {
358
+ private runtime: HostRuntime
359
+
360
+ constructor(
361
+ private readonly detector: HostRuntimeDetector = detectHostRuntime,
362
+ ) {
363
+ this.runtime = detector()
364
+ }
365
+
366
+ current(): HostRuntime {
367
+ return this.runtime
368
+ }
369
+
370
+ reset(): void {
371
+ this.runtime = this.detector()
372
+ }
373
+ }
374
+
375
+ export const hostRuntimeOwner = new HostRuntimeOwner()
376
+
377
+ export function getHostRuntime(): HostRuntime {
378
+ return hostRuntimeOwner.current()
379
+ }
287
380
 
288
381
  export function resetHostRuntimeForTests(): void {
289
- hostRuntime = detectHostRuntime()
382
+ hostRuntimeOwner.reset()
290
383
  }
291
384
 
292
385
  export function writeHostStdoutText(data: string): void {
293
- hostRuntime.writeStdoutText(data)
386
+ getHostRuntime().writeStdoutText(data)
294
387
  }
295
388
 
296
389
  export function writeHostStderrText(data: string): void {
297
- hostRuntime.writeStderrText(data)
390
+ getHostRuntime().writeStderrText(data)
391
+ }
392
+
393
+ export function isMainScript(meta: MainScriptMeta): boolean {
394
+ if (meta.main === true) {
395
+ return true
396
+ }
397
+
398
+ const entryPath = getHostRuntime().processObj?.argv?.[1]
399
+ if (typeof entryPath !== 'string' || entryPath === '') {
400
+ return false
401
+ }
402
+
403
+ const entryURL = fileURLFromScriptPath(entryPath)
404
+ if (!entryURL) {
405
+ return false
406
+ }
407
+
408
+ try {
409
+ return new URL(meta.url).href === entryURL
410
+ } catch {
411
+ return meta.url === entryURL
412
+ }
298
413
  }
@@ -6,3 +6,4 @@ export * from './type.js'
6
6
  export * from './varRef.js'
7
7
  export * from './defer.js'
8
8
  export * from './errors.js'
9
+ export * from './hostio.js'