goscript 0.0.82 → 0.0.84

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 (85) hide show
  1. package/dist/gs/builtin/builtin.js +4 -7
  2. package/dist/gs/builtin/builtin.js.map +1 -1
  3. package/dist/gs/builtin/hostio.d.ts +72 -0
  4. package/dist/gs/builtin/hostio.js +181 -0
  5. package/dist/gs/builtin/hostio.js.map +1 -0
  6. package/dist/gs/builtin/print.d.ts +8 -0
  7. package/dist/gs/builtin/print.js +111 -0
  8. package/dist/gs/builtin/print.js.map +1 -0
  9. package/dist/gs/fmt/fmt.js +2 -22
  10. package/dist/gs/fmt/fmt.js.map +1 -1
  11. package/dist/gs/os/dir.gs.js +41 -27
  12. package/dist/gs/os/dir.gs.js.map +1 -1
  13. package/dist/gs/os/exec.gs.js +1 -2
  14. package/dist/gs/os/exec.gs.js.map +1 -1
  15. package/dist/gs/os/exec_posix.gs.js +1 -1
  16. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  17. package/dist/gs/os/file_constants_js.gs.js +71 -7
  18. package/dist/gs/os/file_constants_js.gs.js.map +1 -1
  19. package/dist/gs/os/file_js.gs.js +168 -24
  20. package/dist/gs/os/file_js.gs.js.map +1 -1
  21. package/dist/gs/os/file_posix_js.gs.js +75 -11
  22. package/dist/gs/os/file_posix_js.gs.js.map +1 -1
  23. package/dist/gs/os/file_unix_js.gs.d.ts +3 -3
  24. package/dist/gs/os/file_unix_js.gs.js +218 -21
  25. package/dist/gs/os/file_unix_js.gs.js.map +1 -1
  26. package/dist/gs/os/getwd_js.gs.js +19 -6
  27. package/dist/gs/os/getwd_js.gs.js.map +1 -1
  28. package/dist/gs/os/path.gs.js +3 -3
  29. package/dist/gs/os/path.gs.js.map +1 -1
  30. package/dist/gs/os/pidfd_js.gs.js +0 -3
  31. package/dist/gs/os/pidfd_js.gs.js.map +1 -1
  32. package/dist/gs/os/proc.gs.js +6 -3
  33. package/dist/gs/os/proc.gs.js.map +1 -1
  34. package/dist/gs/os/proc_js.gs.js +10 -5
  35. package/dist/gs/os/proc_js.gs.js.map +1 -1
  36. package/dist/gs/os/rawconn_js.gs.d.ts +6 -3
  37. package/dist/gs/os/rawconn_js.gs.js +16 -10
  38. package/dist/gs/os/rawconn_js.gs.js.map +1 -1
  39. package/dist/gs/os/removeall_js.gs.js +2 -4
  40. package/dist/gs/os/removeall_js.gs.js.map +1 -1
  41. package/dist/gs/os/root_js.gs.d.ts +4 -1
  42. package/dist/gs/os/root_js.gs.js +90 -40
  43. package/dist/gs/os/root_js.gs.js.map +1 -1
  44. package/dist/gs/os/root_noopenat.gs.js +13 -16
  45. package/dist/gs/os/root_noopenat.gs.js.map +1 -1
  46. package/dist/gs/os/stat_js.gs.js +40 -4
  47. package/dist/gs/os/stat_js.gs.js.map +1 -1
  48. package/dist/gs/os/stat_unix_js.gs.d.ts +1 -1
  49. package/dist/gs/os/stat_unix_js.gs.js +5 -10
  50. package/dist/gs/os/stat_unix_js.gs.js.map +1 -1
  51. package/dist/gs/os/tempfile.gs.d.ts +1 -1
  52. package/dist/gs/os/tempfile.gs.js +24 -7
  53. package/dist/gs/os/tempfile.gs.js.map +1 -1
  54. package/dist/gs/os/types_js.gs.d.ts +30 -2
  55. package/dist/gs/os/types_js.gs.js +426 -23
  56. package/dist/gs/os/types_js.gs.js.map +1 -1
  57. package/gs/builtin/builtin.ts +4 -6
  58. package/gs/builtin/hostio.test.ts +69 -0
  59. package/gs/builtin/hostio.ts +298 -0
  60. package/gs/builtin/print.test.ts +48 -0
  61. package/gs/builtin/print.ts +154 -0
  62. package/gs/fmt/fmt.test.ts +41 -30
  63. package/gs/fmt/fmt.ts +2 -22
  64. package/gs/os/dir.gs.ts +40 -28
  65. package/gs/os/exec.gs.ts +2 -4
  66. package/gs/os/exec_posix.gs.ts +1 -2
  67. package/gs/os/file_constants_js.gs.ts +70 -8
  68. package/gs/os/file_js.gs.ts +156 -26
  69. package/gs/os/file_posix_js.gs.ts +69 -13
  70. package/gs/os/file_unix_js.gs.ts +212 -24
  71. package/gs/os/file_unix_js.test.ts +184 -0
  72. package/gs/os/getwd_js.gs.ts +18 -8
  73. package/gs/os/path.gs.ts +3 -4
  74. package/gs/os/pidfd_js.gs.ts +3 -8
  75. package/gs/os/proc.gs.ts +6 -4
  76. package/gs/os/proc_js.gs.ts +11 -8
  77. package/gs/os/rawconn_js.gs.ts +22 -14
  78. package/gs/os/removeall_js.gs.ts +3 -6
  79. package/gs/os/root_js.gs.ts +94 -42
  80. package/gs/os/root_noopenat.gs.ts +13 -20
  81. package/gs/os/stat_js.gs.ts +37 -6
  82. package/gs/os/stat_unix_js.gs.ts +6 -13
  83. package/gs/os/tempfile.gs.ts +27 -9
  84. package/gs/os/types_js.gs.ts +449 -26
  85. package/package.json +1 -1
@@ -0,0 +1,298 @@
1
+ export class HostUnsupportedError extends Error {
2
+ constructor() {
3
+ super('operation not implemented in JavaScript environment')
4
+ }
5
+ }
6
+
7
+ export type NodeFSModule = {
8
+ readSync(
9
+ fd: number,
10
+ buffer: Uint8Array,
11
+ offset?: number,
12
+ length?: number,
13
+ position?: number | null,
14
+ ): number
15
+ writeSync(
16
+ fd: number,
17
+ buffer: Uint8Array,
18
+ offset?: number,
19
+ length?: number,
20
+ position?: number | null,
21
+ ): number
22
+ closeSync?(fd: number): void
23
+ fstatSync?(fd: number): any
24
+ fsyncSync?(fd: number): void
25
+ ftruncateSync?(fd: number, len?: number): void
26
+ openSync?(path: string, flags: number | string, mode?: number): number
27
+ chmodSync?(path: string, mode: number): void
28
+ chownSync?(path: string, uid: number, gid: number): void
29
+ lchownSync?(path: string, uid: number, gid: number): void
30
+ linkSync?(existingPath: string, newPath: string): void
31
+ lstatSync?(path: string): any
32
+ mkdirSync?(path: string, options?: number | { mode?: number; recursive?: boolean }): void
33
+ readFileSync?(path: string): Uint8Array
34
+ readdirSync?(path: string, options?: { withFileTypes?: boolean }): any[]
35
+ readlinkSync?(path: string): string
36
+ renameSync?(oldPath: string, newPath: string): void
37
+ rmSync?(path: string, options?: { force?: boolean; recursive?: boolean }): void
38
+ rmdirSync?(path: string): void
39
+ statSync?(path: string): any
40
+ symlinkSync?(target: string, path: string): void
41
+ truncateSync?(path: string, len?: number): void
42
+ unlinkSync?(path: string): void
43
+ utimesSync?(path: string, atime: Date | number, mtime: Date | number): void
44
+ writeFileSync?(path: string, data: Uint8Array, options?: { mode?: number }): void
45
+ }
46
+
47
+ export type DenoStream = {
48
+ readSync?(buffer: Uint8Array): number | null
49
+ writeSync?(buffer: Uint8Array): number
50
+ }
51
+
52
+ export type DenoFileLike = DenoStream & {
53
+ close?(): void
54
+ rid?: number
55
+ seekSync?(offset: number, whence: number): number
56
+ syncSync?(): void
57
+ statSync?(): any
58
+ truncateSync?(len?: number): void
59
+ }
60
+
61
+ type HostReadFD = (fd: number, buffer: Uint8Array) => number | null
62
+ type HostWriteFD = (fd: number, buffer: Uint8Array) => number
63
+ type HostTextWrite = (data: string) => void
64
+
65
+ export type HostRuntime = {
66
+ deno: any | null
67
+ nodeFS: NodeFSModule | null
68
+ platform: string
69
+ processObj: any | null
70
+ getEnv(name: string): string
71
+ getStdioHandle(fd: number): DenoFileLike | null
72
+ readFD: HostReadFD
73
+ writeFD: HostWriteFD
74
+ writeStderrText: HostTextWrite
75
+ writeStdoutText: HostTextWrite
76
+ }
77
+
78
+ const encoder = new TextEncoder()
79
+
80
+ function getDynamicRequire(): ((specifier: string) => unknown) | null {
81
+ try {
82
+ return Function(
83
+ "return typeof require !== 'undefined' ? require : null",
84
+ )() as ((specifier: string) => unknown) | null
85
+ } catch {
86
+ return null
87
+ }
88
+ }
89
+
90
+ function writeAllSync(
91
+ writeChunk: (chunk: Uint8Array) => number,
92
+ buffer: Uint8Array,
93
+ ): number {
94
+ let offset = 0
95
+ while (offset < buffer.length) {
96
+ const n = writeChunk(buffer.subarray(offset))
97
+ if (!Number.isFinite(n) || n < 0) {
98
+ throw new Error(`invalid write result: ${n}`)
99
+ }
100
+ if (n === 0) {
101
+ throw new Error('short write')
102
+ }
103
+ offset += n
104
+ }
105
+ return buffer.length
106
+ }
107
+
108
+ function writeAllText(
109
+ writeChunk: (chunk: Uint8Array) => number,
110
+ data: string,
111
+ ): void {
112
+ const bytes = encoder.encode(data)
113
+ if (bytes.length === 0) {
114
+ return
115
+ }
116
+ writeAllSync(writeChunk, bytes)
117
+ }
118
+
119
+ function detectNodeFS(processObj: any | null): NodeFSModule | null {
120
+ if (processObj && typeof processObj.getBuiltinModule === 'function') {
121
+ const module = processObj.getBuiltinModule('fs')
122
+ if (
123
+ module &&
124
+ typeof module.readSync === 'function' &&
125
+ typeof module.writeSync === 'function'
126
+ ) {
127
+ return module as NodeFSModule
128
+ }
129
+ }
130
+
131
+ const requireFn = getDynamicRequire()
132
+ if (requireFn) {
133
+ for (const specifier of ['node:fs', 'fs']) {
134
+ try {
135
+ const module = requireFn(specifier) as NodeFSModule | null
136
+ if (
137
+ module &&
138
+ typeof module.readSync === 'function' &&
139
+ typeof module.writeSync === 'function'
140
+ ) {
141
+ return module
142
+ }
143
+ } catch {
144
+ // Try the next fallback.
145
+ }
146
+ }
147
+ }
148
+
149
+ return null
150
+ }
151
+
152
+ function unsupportedReadFD(_fd: number, _buffer: Uint8Array): number | null {
153
+ throw new HostUnsupportedError()
154
+ }
155
+
156
+ function unsupportedWriteFD(_fd: number, _buffer: Uint8Array): number {
157
+ throw new HostUnsupportedError()
158
+ }
159
+
160
+ function fallbackConsoleWriter(method: 'error' | 'log'): HostTextWrite {
161
+ return (data: string) => {
162
+ const consoleMethod = (globalThis as any).console?.[method]
163
+ if (!consoleMethod) {
164
+ return
165
+ }
166
+ if (data.endsWith('\n')) {
167
+ consoleMethod.call((globalThis as any).console, data.slice(0, -1))
168
+ return
169
+ }
170
+ consoleMethod.call((globalThis as any).console, data)
171
+ }
172
+ }
173
+
174
+ function detectHostRuntime(): HostRuntime {
175
+ const globalObj = globalThis as any
176
+ const deno = globalObj.Deno ?? null
177
+ const processObj = globalObj.process ?? null
178
+ const nodeFS = detectNodeFS(processObj)
179
+
180
+ const getStdioHandle = (fd: number): DenoFileLike | null => {
181
+ if (!deno) {
182
+ return null
183
+ }
184
+ switch (fd) {
185
+ case 0:
186
+ return deno.stdin ?? null
187
+ case 1:
188
+ return deno.stdout ?? null
189
+ case 2:
190
+ return deno.stderr ?? null
191
+ default:
192
+ return null
193
+ }
194
+ }
195
+
196
+ const platform =
197
+ deno?.build?.os ??
198
+ processObj?.platform ??
199
+ 'unknown'
200
+
201
+ const getEnv = (name: string): string => {
202
+ if (deno?.env?.get) {
203
+ try {
204
+ return deno.env.get(name) ?? ''
205
+ } catch {
206
+ return ''
207
+ }
208
+ }
209
+ return processObj?.env?.[name] ?? ''
210
+ }
211
+
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
+ }
223
+ return handle.readSync(buffer)
224
+ }
225
+ writeFD = (fd: number, buffer: Uint8Array): number => {
226
+ const handle = getStdioHandle(fd)
227
+ if (!handle || typeof handle.writeSync !== 'function') {
228
+ throw new HostUnsupportedError()
229
+ }
230
+ return writeAllSync(
231
+ (chunk: Uint8Array) => handle.writeSync!(chunk),
232
+ buffer,
233
+ )
234
+ }
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(
256
+ (chunk: Uint8Array) =>
257
+ nodeFS.writeSync(fd, chunk, 0, chunk.length, null),
258
+ buffer,
259
+ )
260
+ writeStdoutText = (data: string) =>
261
+ writeAllText(
262
+ (chunk: Uint8Array) => nodeFS.writeSync(1, chunk, 0, chunk.length, null),
263
+ data,
264
+ )
265
+ writeStderrText = (data: string) =>
266
+ writeAllText(
267
+ (chunk: Uint8Array) => nodeFS.writeSync(2, chunk, 0, chunk.length, null),
268
+ data,
269
+ )
270
+ }
271
+
272
+ return {
273
+ deno,
274
+ getEnv,
275
+ getStdioHandle,
276
+ nodeFS,
277
+ platform,
278
+ processObj,
279
+ readFD,
280
+ writeFD,
281
+ writeStderrText,
282
+ writeStdoutText,
283
+ }
284
+ }
285
+
286
+ export let hostRuntime = detectHostRuntime()
287
+
288
+ export function resetHostRuntimeForTests(): void {
289
+ hostRuntime = detectHostRuntime()
290
+ }
291
+
292
+ export function writeHostStdoutText(data: string): void {
293
+ hostRuntime.writeStdoutText(data)
294
+ }
295
+
296
+ export function writeHostStderrText(data: string): void {
297
+ hostRuntime.writeStderrText(data)
298
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { formatPrintedArgs } from './print.js'
4
+
5
+ describe('builtin println formatting', () => {
6
+ it('formats Uint8Array values with a stable inspect-style representation', () => {
7
+ expect(
8
+ formatPrintedArgs(['b2:', new Uint8Array([72, 101, 108, 108, 111])]),
9
+ ).toBe('b2: Uint8Array(5) [ 72, 101, 108, 108, 111 ]')
10
+ })
11
+
12
+ it('quotes nested string array elements', () => {
13
+ expect(formatPrintedArgs(['strings.Split:', ['a', 'b', 'c']])).toBe(
14
+ 'strings.Split: [ "a", "b", "c" ]',
15
+ )
16
+ })
17
+
18
+ it('formats plain objects across multiple lines', () => {
19
+ expect(
20
+ formatPrintedArgs([
21
+ 'out:',
22
+ {
23
+ exampleField: new Uint8Array([104, 101, 108, 108, 111]),
24
+ exampleText: 'world',
25
+ },
26
+ ]),
27
+ ).toBe(`out: {
28
+ exampleField: Uint8Array(5) [ 104, 101, 108, 108, 111 ],
29
+ exampleText: "world",
30
+ }`)
31
+ })
32
+
33
+ it('formats goscript struct field bags using field values', () => {
34
+ expect(
35
+ formatPrintedArgs([
36
+ {
37
+ _fields: {
38
+ Name: { value: 'hello' },
39
+ Count: { value: 3 },
40
+ },
41
+ },
42
+ ]),
43
+ ).toBe(`{
44
+ Name: "hello",
45
+ Count: 3,
46
+ }`)
47
+ })
48
+ })
@@ -0,0 +1,154 @@
1
+ import { asArray, isSliceProxy, type Slice } from './slice.js'
2
+
3
+ /**
4
+ * formatPrintedArgs formats builtin println arguments deterministically.
5
+ */
6
+ export function formatPrintedArgs(args: readonly any[]): string {
7
+ return args.map((arg) => formatPrintedValue(arg)).join(' ')
8
+ }
9
+
10
+ /**
11
+ * formatPrintedValue formats a single builtin println argument deterministically.
12
+ */
13
+ export function formatPrintedValue(value: any): string {
14
+ return formatValue(value, 0, false, new WeakSet<object>())
15
+ }
16
+
17
+ function formatValue(
18
+ value: any,
19
+ depth: number,
20
+ nested: boolean,
21
+ seen: WeakSet<object>,
22
+ ): string {
23
+ if (value === null) {
24
+ return 'null'
25
+ }
26
+
27
+ if (value === undefined) {
28
+ return '<nil>'
29
+ }
30
+
31
+ if (typeof value === 'string') {
32
+ return nested ? JSON.stringify(value) : value
33
+ }
34
+
35
+ if (typeof value === 'boolean' || typeof value === 'number') {
36
+ return String(value)
37
+ }
38
+
39
+ if (typeof value === 'bigint') {
40
+ return value.toString()
41
+ }
42
+
43
+ if (value instanceof Uint8Array) {
44
+ return formatUint8Array(value)
45
+ }
46
+
47
+ if (Array.isArray(value)) {
48
+ return formatArray(value, depth, seen)
49
+ }
50
+
51
+ if (isSliceProxy(value as Slice<unknown>)) {
52
+ return formatArray(asArray(value as Slice<unknown>), depth, seen)
53
+ }
54
+
55
+ if (typeof value === 'function') {
56
+ return value.name ? `[Function: ${value.name}]` : '[Function]'
57
+ }
58
+
59
+ if (typeof value !== 'object') {
60
+ return String(value)
61
+ }
62
+
63
+ if (seen.has(value)) {
64
+ return '[Circular]'
65
+ }
66
+ seen.add(value)
67
+
68
+ try {
69
+ if (value instanceof Map) {
70
+ return formatArray(
71
+ Array.from(value.entries()).map(([k, v]) => `${formatValue(k, depth + 1, true, seen)} => ${formatValue(v, depth + 1, true, seen)}`),
72
+ depth,
73
+ seen,
74
+ )
75
+ }
76
+
77
+ if (value instanceof Set) {
78
+ return formatArray(Array.from(value.values()), depth, seen)
79
+ }
80
+
81
+ if (value instanceof Error) {
82
+ return value.message || value.toString()
83
+ }
84
+
85
+ if (typeof value.GoString === 'function') {
86
+ return value.GoString()
87
+ }
88
+
89
+ if (typeof value.Error === 'function') {
90
+ return value.Error()
91
+ }
92
+
93
+ if (typeof value.String === 'function') {
94
+ return value.String()
95
+ }
96
+
97
+ const entries = getObjectEntries(value)
98
+ if (entries.length === 0) {
99
+ return '{}'
100
+ }
101
+
102
+ return formatObject(entries, depth, seen)
103
+ } finally {
104
+ seen.delete(value)
105
+ }
106
+ }
107
+
108
+ function formatUint8Array(value: Uint8Array): string {
109
+ if (value.length === 0) {
110
+ return 'Uint8Array(0) []'
111
+ }
112
+
113
+ return `Uint8Array(${value.length}) [ ${Array.from(value).join(', ')} ]`
114
+ }
115
+
116
+ function formatArray(value: readonly any[], depth: number, seen: WeakSet<object>): string {
117
+ if (value.length === 0) {
118
+ return '[]'
119
+ }
120
+
121
+ return `[ ${value.map((item) => formatValue(item, depth + 1, true, seen)).join(', ')} ]`
122
+ }
123
+
124
+ function formatObject(
125
+ entries: readonly [string, any][],
126
+ depth: number,
127
+ seen: WeakSet<object>,
128
+ ): string {
129
+ const pad = ' '.repeat(depth + 1)
130
+ const closePad = ' '.repeat(depth)
131
+ const body = entries
132
+ .map(
133
+ ([key, value]) =>
134
+ `${pad}${key}: ${formatValue(value, depth + 1, true, seen)},`,
135
+ )
136
+ .join('\n')
137
+
138
+ return `{\n${body}\n${closePad}}`
139
+ }
140
+
141
+ function getObjectEntries(value: Record<string, any>): [string, any][] {
142
+ const fields = value._fields
143
+ if (fields && typeof fields === 'object' && !Array.isArray(fields)) {
144
+ return Object.keys(fields).map((key) => {
145
+ const field = fields[key]
146
+ if (field && typeof field === 'object' && 'value' in field) {
147
+ return [key, field.value]
148
+ }
149
+ return [key, field]
150
+ })
151
+ }
152
+
153
+ return Object.entries(value).filter(([, entry]) => typeof entry !== 'function')
154
+ }
@@ -1,38 +1,49 @@
1
- import { describe, it, expect } from 'vitest'
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import { resetHostRuntimeForTests } from '@goscript/builtin/hostio.js'
2
3
  import * as fmt from './fmt.js'
3
4
 
4
- // Helper to capture stdout via internal stdout.write
5
- // We will monkey-patch global process.stdout.write if available
5
+ const originalDeno = (globalThis as any).Deno
6
+ const originalProcess = (globalThis as any).process
7
+
8
+ afterEach(() => {
9
+ if (originalDeno === undefined) {
10
+ delete (globalThis as any).Deno
11
+ } else {
12
+ ;(globalThis as any).Deno = originalDeno
13
+ }
14
+
15
+ if (originalProcess === undefined) {
16
+ delete (globalThis as any).process
17
+ } else {
18
+ ;(globalThis as any).process = originalProcess
19
+ }
20
+
21
+ resetHostRuntimeForTests()
22
+ })
23
+
24
+ // Helper to capture stdout via the hostio text output path.
6
25
  function captureStdout(run: () => void): string {
7
26
  let buf = ''
8
- const hasProcess =
9
- typeof process !== 'undefined' &&
10
- (process as any).stdout &&
11
- typeof (process as any).stdout.write === 'function'
12
-
13
- if (hasProcess) {
14
- const orig = (process as any).stdout.write
15
- ;(process as any).stdout.write = (chunk: any) => {
16
- buf += typeof chunk === 'string' ? chunk : String(chunk)
17
- return true
18
- }
19
- try {
20
- run()
21
- } finally {
22
- ;(process as any).stdout.write = orig
23
- }
24
- } else {
25
- // Fallback: spy on console.log for environments without process
26
- const origLog = console.log
27
- ;(console as any).log = (msg: any) => {
28
- buf += String(msg) + '\n'
29
- }
30
- try {
31
- run()
32
- } finally {
33
- console.log = origLog
34
- }
27
+ delete (globalThis as any).Deno
28
+ ;(globalThis as any).process = {
29
+ getBuiltinModule: vi.fn(() => ({
30
+ readSync: vi.fn(),
31
+ writeSync: vi.fn(
32
+ (
33
+ _fd: number,
34
+ chunk: Uint8Array,
35
+ _offset?: number,
36
+ length?: number,
37
+ _position?: number | null,
38
+ ) => {
39
+ buf += new TextDecoder().decode(chunk.subarray(0, length ?? chunk.length))
40
+ return length ?? chunk.length
41
+ },
42
+ ),
43
+ })),
35
44
  }
45
+ resetHostRuntimeForTests()
46
+ run()
36
47
 
37
48
  return buf
38
49
  }
package/gs/fmt/fmt.ts CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  import * as $ from '@goscript/builtin/index.js'
5
5
  import * as errors from '@goscript/errors/index.js'
6
+ import { writeHostStdoutText } from '@goscript/builtin/hostio.js'
6
7
 
7
8
  // Basic interfaces
8
9
  export interface Stringer {
@@ -259,28 +260,7 @@ function parseFormat(format: string, args: any[]): string {
259
260
  // Global stdout simulation for Print functions
260
261
  let stdout = {
261
262
  write: (data: string) => {
262
- // Use process.stdout.write if available (Node.js), otherwise fallback to console.log
263
- // but we need to avoid adding extra newlines that console.log adds
264
- if (
265
- typeof process !== 'undefined' &&
266
- process.stdout &&
267
- process.stdout.write
268
- ) {
269
- process.stdout.write(data)
270
- } else {
271
- // In browser environments, we need to use console.log but handle newlines carefully
272
- // If the data already ends with \n, we should strip it to avoid double newlines
273
- if (data.endsWith('\n')) {
274
- console.log(data.slice(0, -1))
275
- } else {
276
- // Use console.log without adding newline by using a custom method
277
- if (console.log) {
278
- // For data without newlines, we can just print it directly
279
- // This is a bit of a hack but works for most cases
280
- console.log(data)
281
- }
282
- }
283
- }
263
+ writeHostStdoutText(data)
284
264
  },
285
265
  }
286
266
 
package/gs/os/dir.gs.ts CHANGED
@@ -1,42 +1,54 @@
1
1
  import * as $ from "@goscript/builtin/index.js";
2
2
  import { ErrUnimplemented } from "./error.gs.js";
3
+ import { createFileInfo, getDeno, getNodeFS, newHostError } from "./types_js.gs.js";
4
+ import { FileInfoToDirEntry } from "@goscript/io/fs/readdir.js";
3
5
 
4
6
  import * as fs from "@goscript/io/fs/index.js"
5
7
 
6
8
  type DirEntry = fs.DirEntry;
7
9
 
8
- // ReadDir reads the named directory,
9
- // returning all its directory entries sorted by filename.
10
- // If an error occurs reading the directory,
11
- // ReadDir returns the entries it was able to read before the error,
12
- // along with the error.
13
10
  export function ReadDir(name: string): [$.Slice<DirEntry>, $.GoError] {
14
- // Directory reading not supported in JavaScript environment
11
+ const denoObj = getDeno()
12
+ if (denoObj?.readDirSync) {
13
+ try {
14
+ const entries: DirEntry[] = []
15
+ for (const entry of denoObj.readDirSync(name)) {
16
+ const dirEntry = FileInfoToDirEntry(createFileInfo(entry.name, {
17
+ isDirectory: () => entry.isDirectory,
18
+ isSymbolicLink: () => entry.isSymlink,
19
+ mode: 0,
20
+ size: 0,
21
+ }))
22
+ if (dirEntry !== null) {
23
+ entries.push(dirEntry)
24
+ }
25
+ }
26
+ entries.sort((a, b) => (a?.Name() ?? "").localeCompare(b?.Name() ?? ""))
27
+ return [$.arrayToSlice(entries), null]
28
+ } catch (err) {
29
+ return [null, newHostError(err)]
30
+ }
31
+ }
32
+ const nodeFS = getNodeFS()
33
+ if (nodeFS?.readdirSync) {
34
+ try {
35
+ const entries = nodeFS.readdirSync(name, { withFileTypes: true }).map((entry: any) =>
36
+ FileInfoToDirEntry(createFileInfo(entry.name, {
37
+ isDirectory: () => typeof entry.isDirectory === "function" ? entry.isDirectory() : false,
38
+ isSymbolicLink: () => typeof entry.isSymbolicLink === "function" ? entry.isSymbolicLink() : false,
39
+ mode: 0,
40
+ size: 0,
41
+ }))
42
+ ).filter((entry: DirEntry | null): entry is DirEntry => entry !== null)
43
+ entries.sort((a, b) => (a?.Name() ?? "").localeCompare(b?.Name() ?? ""))
44
+ return [$.arrayToSlice(entries), null]
45
+ } catch (err) {
46
+ return [null, newHostError(err)]
47
+ }
48
+ }
15
49
  return [null, ErrUnimplemented]
16
50
  }
17
51
 
18
- // CopyFS copies the file system fsys into the directory dir,
19
- // creating dir if necessary.
20
- //
21
- // Files are created with mode 0o666 plus any execute permissions
22
- // from the source, and directories are created with mode 0o777
23
- // (before umask).
24
- //
25
- // CopyFS will not overwrite existing files. If a file name in fsys
26
- // already exists in the destination, CopyFS will return an error
27
- // such that errors.Is(err, fs.ErrExist) will be true.
28
- //
29
- // Symbolic links in fsys are not supported. A *PathError with Err set
30
- // to ErrInvalid is returned when copying from a symbolic link.
31
- //
32
- // Symbolic links in dir are followed.
33
- //
34
- // New files added to fsys (including if dir is a subdirectory of fsys)
35
- // while CopyFS is running are not guaranteed to be copied.
36
- //
37
- // Copying stops at and returns the first error encountered.
38
52
  export function CopyFS(dir: string, fsys: fs.FS): $.GoError {
39
- // File system copying not supported in JavaScript environment
40
53
  return ErrUnimplemented
41
54
  }
42
-