@ecmaos/coreutils 0.1.5 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +45 -0
- package/dist/commands/basename.d.ts +4 -0
- package/dist/commands/basename.d.ts.map +1 -0
- package/dist/commands/basename.js +78 -0
- package/dist/commands/basename.js.map +1 -0
- package/dist/commands/cal.d.ts +4 -0
- package/dist/commands/cal.d.ts.map +1 -0
- package/dist/commands/cal.js +105 -0
- package/dist/commands/cal.js.map +1 -0
- package/dist/commands/cat.d.ts.map +1 -1
- package/dist/commands/cat.js +33 -25
- package/dist/commands/cat.js.map +1 -1
- package/dist/commands/cd.d.ts.map +1 -1
- package/dist/commands/cd.js +38 -10
- package/dist/commands/cd.js.map +1 -1
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +31 -9
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/comm.d.ts +4 -0
- package/dist/commands/comm.d.ts.map +1 -0
- package/dist/commands/comm.js +162 -0
- package/dist/commands/comm.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +61 -12
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cut.d.ts +4 -0
- package/dist/commands/cut.d.ts.map +1 -0
- package/dist/commands/cut.js +208 -0
- package/dist/commands/cut.js.map +1 -0
- package/dist/commands/date.d.ts +4 -0
- package/dist/commands/date.d.ts.map +1 -0
- package/dist/commands/date.js +100 -0
- package/dist/commands/date.js.map +1 -0
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +194 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/dirname.d.ts +4 -0
- package/dist/commands/dirname.d.ts.map +1 -0
- package/dist/commands/dirname.js +50 -0
- package/dist/commands/dirname.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +49 -10
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/false.d.ts +4 -0
- package/dist/commands/false.d.ts.map +1 -0
- package/dist/commands/false.js +27 -0
- package/dist/commands/false.js.map +1 -0
- package/dist/commands/find.d.ts +4 -0
- package/dist/commands/find.d.ts.map +1 -0
- package/dist/commands/find.js +181 -0
- package/dist/commands/find.js.map +1 -0
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +45 -10
- package/dist/commands/grep.js.map +1 -1
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +46 -9
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hex.d.ts.map +1 -1
- package/dist/commands/hex.js +16 -6
- package/dist/commands/hex.js.map +1 -1
- package/dist/commands/id.d.ts +4 -0
- package/dist/commands/id.d.ts.map +1 -0
- package/dist/commands/id.js +97 -0
- package/dist/commands/id.js.map +1 -0
- package/dist/commands/join.d.ts +4 -0
- package/dist/commands/join.d.ts.map +1 -0
- package/dist/commands/join.js +152 -0
- package/dist/commands/join.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +16 -7
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/ln.d.ts.map +1 -1
- package/dist/commands/ln.js +54 -12
- package/dist/commands/ln.js.map +1 -1
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +96 -91
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/mkdir.d.ts.map +1 -1
- package/dist/commands/mkdir.js +34 -9
- package/dist/commands/mkdir.js.map +1 -1
- package/dist/commands/mv.d.ts.map +1 -1
- package/dist/commands/mv.js +25 -7
- package/dist/commands/mv.js.map +1 -1
- package/dist/commands/nc.d.ts +4 -0
- package/dist/commands/nc.d.ts.map +1 -0
- package/dist/commands/nc.js +451 -0
- package/dist/commands/nc.js.map +1 -0
- package/dist/commands/nl.d.ts +4 -0
- package/dist/commands/nl.d.ts.map +1 -0
- package/dist/commands/nl.js +208 -0
- package/dist/commands/nl.js.map +1 -0
- package/dist/commands/passkey.d.ts.map +1 -1
- package/dist/commands/passkey.js +44 -18
- package/dist/commands/passkey.js.map +1 -1
- package/dist/commands/paste.d.ts +4 -0
- package/dist/commands/paste.d.ts.map +1 -0
- package/dist/commands/paste.js +161 -0
- package/dist/commands/paste.js.map +1 -0
- package/dist/commands/pwd.d.ts.map +1 -1
- package/dist/commands/pwd.js +13 -4
- package/dist/commands/pwd.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +105 -12
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/rmdir.d.ts.map +1 -1
- package/dist/commands/rmdir.js +34 -9
- package/dist/commands/rmdir.js.map +1 -1
- package/dist/commands/sed.d.ts.map +1 -1
- package/dist/commands/sed.js +73 -28
- package/dist/commands/sed.js.map +1 -1
- package/dist/commands/seq.d.ts +4 -0
- package/dist/commands/seq.d.ts.map +1 -0
- package/dist/commands/seq.js +148 -0
- package/dist/commands/seq.js.map +1 -0
- package/dist/commands/sockets.d.ts +4 -0
- package/dist/commands/sockets.d.ts.map +1 -0
- package/dist/commands/sockets.js +239 -0
- package/dist/commands/sockets.js.map +1 -0
- package/dist/commands/sort.d.ts +4 -0
- package/dist/commands/sort.d.ts.map +1 -0
- package/dist/commands/sort.js +175 -0
- package/dist/commands/sort.js.map +1 -0
- package/dist/commands/split.d.ts +4 -0
- package/dist/commands/split.d.ts.map +1 -0
- package/dist/commands/split.js +147 -0
- package/dist/commands/split.js.map +1 -0
- package/dist/commands/stat.d.ts.map +1 -1
- package/dist/commands/stat.js +14 -6
- package/dist/commands/stat.js.map +1 -1
- package/dist/commands/tail.d.ts.map +1 -1
- package/dist/commands/tail.js +46 -9
- package/dist/commands/tail.js.map +1 -1
- package/dist/commands/tee.d.ts.map +1 -1
- package/dist/commands/tee.js +45 -10
- package/dist/commands/tee.js.map +1 -1
- package/dist/commands/test.d.ts +4 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +99 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/touch.d.ts.map +1 -1
- package/dist/commands/touch.js +34 -9
- package/dist/commands/touch.js.map +1 -1
- package/dist/commands/tr.d.ts +4 -0
- package/dist/commands/tr.d.ts.map +1 -0
- package/dist/commands/tr.js +162 -0
- package/dist/commands/tr.js.map +1 -0
- package/dist/commands/true.d.ts +4 -0
- package/dist/commands/true.d.ts.map +1 -0
- package/dist/commands/true.js +27 -0
- package/dist/commands/true.js.map +1 -0
- package/dist/commands/uniq.d.ts +4 -0
- package/dist/commands/uniq.d.ts.map +1 -0
- package/dist/commands/uniq.js +187 -0
- package/dist/commands/uniq.js.map +1 -0
- package/dist/commands/user.d.ts +4 -0
- package/dist/commands/user.d.ts.map +1 -0
- package/dist/commands/user.js +427 -0
- package/dist/commands/user.js.map +1 -0
- package/dist/commands/wc.d.ts +4 -0
- package/dist/commands/wc.d.ts.map +1 -0
- package/dist/commands/wc.js +178 -0
- package/dist/commands/wc.js.map +1 -0
- package/dist/commands/which.d.ts +4 -0
- package/dist/commands/which.d.ts.map +1 -0
- package/dist/commands/which.js +78 -0
- package/dist/commands/which.js.map +1 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +28 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/terminal-command.d.ts +8 -2
- package/dist/shared/terminal-command.d.ts.map +1 -1
- package/dist/shared/terminal-command.js +42 -17
- package/dist/shared/terminal-command.js.map +1 -1
- package/package.json +4 -2
- package/src/commands/basename.ts +84 -0
- package/src/commands/cal.ts +111 -0
- package/src/commands/cat.ts +37 -27
- package/src/commands/cd.ts +40 -12
- package/src/commands/chmod.ts +37 -11
- package/src/commands/comm.ts +169 -0
- package/src/commands/cp.ts +73 -15
- package/src/commands/cut.ts +214 -0
- package/src/commands/date.ts +97 -0
- package/src/commands/diff.ts +204 -0
- package/src/commands/dirname.ts +57 -0
- package/src/commands/echo.ts +51 -11
- package/src/commands/false.ts +31 -0
- package/src/commands/find.ts +184 -0
- package/src/commands/grep.ts +44 -11
- package/src/commands/head.ts +46 -10
- package/src/commands/hex.ts +19 -7
- package/src/commands/id.ts +94 -0
- package/src/commands/join.ts +162 -0
- package/src/commands/less.ts +20 -8
- package/src/commands/ln.ts +50 -13
- package/src/commands/ls.ts +110 -87
- package/src/commands/mkdir.ts +41 -12
- package/src/commands/mv.ts +31 -9
- package/src/commands/nc.ts +499 -0
- package/src/commands/nl.ts +201 -0
- package/src/commands/passkey.ts +46 -19
- package/src/commands/paste.ts +172 -0
- package/src/commands/pwd.ts +16 -4
- package/src/commands/rm.ts +118 -13
- package/src/commands/rmdir.ts +41 -12
- package/src/commands/sed.ts +64 -30
- package/src/commands/seq.ts +147 -0
- package/src/commands/sockets.ts +283 -0
- package/src/commands/sort.ts +175 -0
- package/src/commands/split.ts +154 -0
- package/src/commands/stat.ts +17 -8
- package/src/commands/tail.ts +46 -10
- package/src/commands/tee.ts +43 -11
- package/src/commands/test.ts +116 -0
- package/src/commands/touch.ts +41 -12
- package/src/commands/tr.ts +170 -0
- package/src/commands/true.ts +31 -0
- package/src/commands/uniq.ts +189 -0
- package/src/commands/user.ts +436 -0
- package/src/commands/wc.ts +181 -0
- package/src/commands/which.ts +89 -0
- package/src/commands/whoami.ts +32 -0
- package/src/index.ts +67 -1
- package/src/shared/terminal-command.ts +43 -16
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
3
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: nc [OPTIONS] <host> [port]
|
|
8
|
+
nc [OPTIONS] -u <url>
|
|
9
|
+
|
|
10
|
+
Netcat - network utility for reading from and writing to network connections.
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
-u, --url <url> Direct URL (ws://, wss://, or https://)
|
|
14
|
+
-p, --port <port> Port number (for WebSocket, defaults to 80/443)
|
|
15
|
+
--help display this help and exit
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
nc echo.websocket.org
|
|
19
|
+
nc -p 443 echo.websocket.org
|
|
20
|
+
nc -u wss://echo.websocket.org
|
|
21
|
+
nc -u https://example.com:443`
|
|
22
|
+
writelnStdout(process, terminal, usage)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ConnectionOptions {
|
|
26
|
+
url: string
|
|
27
|
+
useWebSocket: boolean
|
|
28
|
+
useWebTransport: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseUrl(args: string[]): ConnectionOptions | null {
|
|
32
|
+
let url: string | undefined
|
|
33
|
+
let host: string | undefined
|
|
34
|
+
let port: number | undefined
|
|
35
|
+
let useUrlFlag = false
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < args.length; i++) {
|
|
38
|
+
const arg = args[i]
|
|
39
|
+
if (arg === undefined) continue
|
|
40
|
+
|
|
41
|
+
if (arg === '-u' || arg === '--url') {
|
|
42
|
+
useUrlFlag = true
|
|
43
|
+
if (i + 1 < args.length) {
|
|
44
|
+
i++
|
|
45
|
+
url = args[i]
|
|
46
|
+
} else {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
} else if (arg === '-p' || arg === '--port') {
|
|
50
|
+
if (i + 1 < args.length) {
|
|
51
|
+
i++
|
|
52
|
+
const portStr = args[i]
|
|
53
|
+
if (portStr !== undefined) {
|
|
54
|
+
port = parseInt(portStr, 10)
|
|
55
|
+
if (isNaN(port)) return null
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
} else if (!arg.startsWith('-')) {
|
|
61
|
+
if (!host) {
|
|
62
|
+
host = arg
|
|
63
|
+
} else if (!port) {
|
|
64
|
+
port = parseInt(arg, 10)
|
|
65
|
+
if (isNaN(port)) return null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (useUrlFlag) {
|
|
71
|
+
if (!url) return null
|
|
72
|
+
const useWebSocket = url.startsWith('ws://') || url.startsWith('wss://')
|
|
73
|
+
const useWebTransport = url.startsWith('https://') && 'WebTransport' in globalThis
|
|
74
|
+
return { url, useWebSocket, useWebTransport }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!host) return null
|
|
78
|
+
|
|
79
|
+
if (host.startsWith('http://') || host.startsWith('https://') || host.startsWith('ws://') || host.startsWith('wss://')) {
|
|
80
|
+
const useWebSocket = host.startsWith('ws://') || host.startsWith('wss://')
|
|
81
|
+
const useWebTransport = host.startsWith('https://') && 'WebTransport' in globalThis
|
|
82
|
+
return { url: host, useWebSocket, useWebTransport }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (port === undefined) {
|
|
86
|
+
port = 80
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const protocol = port === 443 ? 'wss' : 'ws'
|
|
90
|
+
let constructedUrl: string
|
|
91
|
+
if (port === 80 && protocol === 'ws') {
|
|
92
|
+
constructedUrl = `${protocol}://${host}`
|
|
93
|
+
} else if (port === 443 && protocol === 'wss') {
|
|
94
|
+
constructedUrl = `${protocol}://${host}`
|
|
95
|
+
} else {
|
|
96
|
+
constructedUrl = `${protocol}://${host}:${port}`
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
url: constructedUrl,
|
|
100
|
+
useWebSocket: true,
|
|
101
|
+
useWebTransport: false
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function connectWebSocket(
|
|
106
|
+
url: string,
|
|
107
|
+
process: Process,
|
|
108
|
+
kernel: Kernel,
|
|
109
|
+
terminal: Terminal
|
|
110
|
+
): Promise<number> {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
let interrupted = false
|
|
113
|
+
let connection: ReturnType<typeof kernel.sockets.get> | null = null
|
|
114
|
+
let stdinReader: ReadableStreamDefaultReader<Uint8Array> | null = null
|
|
115
|
+
let stdoutWriter: WritableStreamDefaultWriter<Uint8Array> | null = null
|
|
116
|
+
let wsClosed = false
|
|
117
|
+
let connectionOpened = false
|
|
118
|
+
let messagesReceived = 0
|
|
119
|
+
let ctrlCListener: { dispose: () => void } | null = null
|
|
120
|
+
|
|
121
|
+
const interruptHandler = () => {
|
|
122
|
+
interrupted = true
|
|
123
|
+
if (ctrlCListener) {
|
|
124
|
+
ctrlCListener.dispose()
|
|
125
|
+
ctrlCListener = null
|
|
126
|
+
}
|
|
127
|
+
if (connection && connection.type === 'websocket') {
|
|
128
|
+
const ws = connection.socket
|
|
129
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
130
|
+
ws.close(1000, 'Interrupted by user')
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (stdinReader) {
|
|
134
|
+
stdinReader.cancel()
|
|
135
|
+
}
|
|
136
|
+
if (stdoutWriter) {
|
|
137
|
+
stdoutWriter.releaseLock()
|
|
138
|
+
}
|
|
139
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
140
|
+
|
|
141
|
+
terminal.listen()
|
|
142
|
+
terminal.write('\n')
|
|
143
|
+
|
|
144
|
+
if (!wsClosed) {
|
|
145
|
+
wsClosed = true
|
|
146
|
+
resolve(1)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
151
|
+
|
|
152
|
+
kernel.sockets.createWebSocket(url)
|
|
153
|
+
.then((conn) => {
|
|
154
|
+
if (interrupted) {
|
|
155
|
+
conn.close()
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
connection = conn
|
|
160
|
+
const ws = conn.socket
|
|
161
|
+
connectionOpened = true
|
|
162
|
+
stdoutWriter = process.stdout.getWriter()
|
|
163
|
+
|
|
164
|
+
terminal.clearCommand()
|
|
165
|
+
terminal.unlisten()
|
|
166
|
+
|
|
167
|
+
ctrlCListener = terminal.onKey(({ domEvent }) => {
|
|
168
|
+
if (domEvent.ctrlKey && domEvent.key === 'c') {
|
|
169
|
+
domEvent.preventDefault()
|
|
170
|
+
domEvent.stopPropagation()
|
|
171
|
+
kernel.terminal.events.dispatch(TerminalEvents.INTERRUPT, { terminal })
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
if (process.stdin) {
|
|
176
|
+
stdinReader = process.stdin.getReader()
|
|
177
|
+
|
|
178
|
+
const readStdin = async () => {
|
|
179
|
+
try {
|
|
180
|
+
while (!interrupted && !wsClosed) {
|
|
181
|
+
const { done, value } = await stdinReader!.read()
|
|
182
|
+
if (done) {
|
|
183
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
184
|
+
ws.close(1000, 'Stdin closed')
|
|
185
|
+
}
|
|
186
|
+
break
|
|
187
|
+
}
|
|
188
|
+
if (ws.readyState === WebSocket.OPEN && value) {
|
|
189
|
+
try {
|
|
190
|
+
ws.send(value)
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (!interrupted) {
|
|
193
|
+
writelnStderr(process, terminal, `Error sending data: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
194
|
+
}
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
} else if (wsClosed || ws.readyState === WebSocket.CLOSED) {
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (!interrupted) {
|
|
203
|
+
writelnStderr(process, terminal, `Error reading stdin: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
204
|
+
}
|
|
205
|
+
} finally {
|
|
206
|
+
if (stdinReader) {
|
|
207
|
+
stdinReader.releaseLock()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
readStdin().catch(() => {})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ws.onmessage = async (event) => {
|
|
216
|
+
if (interrupted || wsClosed || !stdoutWriter) return
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
messagesReceived++
|
|
220
|
+
let data: Uint8Array
|
|
221
|
+
if (event.data instanceof ArrayBuffer) {
|
|
222
|
+
data = new Uint8Array(event.data)
|
|
223
|
+
} else if (event.data instanceof Blob) {
|
|
224
|
+
const arrayBuffer = await event.data.arrayBuffer()
|
|
225
|
+
data = new Uint8Array(arrayBuffer)
|
|
226
|
+
} else {
|
|
227
|
+
const encoder = new TextEncoder()
|
|
228
|
+
data = encoder.encode(event.data as string)
|
|
229
|
+
}
|
|
230
|
+
await stdoutWriter.write(data)
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (!interrupted && !wsClosed) {
|
|
233
|
+
writelnStderr(process, terminal, `Error writing to stdout: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
ws.onclose = (event) => {
|
|
239
|
+
if (wsClosed) return
|
|
240
|
+
wsClosed = true
|
|
241
|
+
|
|
242
|
+
if (ctrlCListener) {
|
|
243
|
+
ctrlCListener.dispose()
|
|
244
|
+
}
|
|
245
|
+
if (stdinReader) {
|
|
246
|
+
stdinReader.cancel().catch(() => {})
|
|
247
|
+
}
|
|
248
|
+
if (stdoutWriter) {
|
|
249
|
+
stdoutWriter.releaseLock()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
253
|
+
|
|
254
|
+
if (!interrupted) {
|
|
255
|
+
if (!connectionOpened) {
|
|
256
|
+
const reason = event.reason || `Connection failed (code: ${event.code})`
|
|
257
|
+
writelnStderr(process, terminal, reason)
|
|
258
|
+
} else if (event.code !== 1000 && event.code !== 1001 && event.code !== 1005 && event.code !== 1006) {
|
|
259
|
+
if (event.reason) {
|
|
260
|
+
writelnStderr(process, terminal, `Connection closed: ${event.reason}`)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
terminal.listen()
|
|
265
|
+
terminal.write('\n')
|
|
266
|
+
|
|
267
|
+
const isNormalClose = event.code === 1000 || event.code === 1001 || event.code === 1005 ||
|
|
268
|
+
(event.code === 1006 && connectionOpened && messagesReceived > 0)
|
|
269
|
+
resolve(isNormalClose ? 0 : 1)
|
|
270
|
+
} else {
|
|
271
|
+
terminal.listen()
|
|
272
|
+
terminal.write('\n')
|
|
273
|
+
resolve(1)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
.catch((error) => {
|
|
278
|
+
if (!interrupted) {
|
|
279
|
+
writelnStderr(process, terminal, `Failed to create WebSocket: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
280
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
281
|
+
|
|
282
|
+
terminal.listen()
|
|
283
|
+
terminal.write('\n')
|
|
284
|
+
|
|
285
|
+
resolve(1)
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function connectWebTransport(
|
|
292
|
+
url: string,
|
|
293
|
+
process: Process,
|
|
294
|
+
kernel: Kernel,
|
|
295
|
+
terminal: Terminal
|
|
296
|
+
): Promise<number> {
|
|
297
|
+
let interrupted = false
|
|
298
|
+
let connection: ReturnType<typeof kernel.sockets.get> | null = null
|
|
299
|
+
let stdoutWriter: WritableStreamDefaultWriter<Uint8Array> | null = null
|
|
300
|
+
let streamReader: ReadableStreamDefaultReader<Uint8Array> | null = null
|
|
301
|
+
let streamWriter: WritableStreamDefaultWriter<Uint8Array> | null = null
|
|
302
|
+
let keyListener: { dispose: () => void } | null = null
|
|
303
|
+
|
|
304
|
+
const interruptHandler = () => {
|
|
305
|
+
interrupted = true
|
|
306
|
+
if (keyListener) {
|
|
307
|
+
keyListener.dispose()
|
|
308
|
+
keyListener = null
|
|
309
|
+
}
|
|
310
|
+
if (streamWriter) {
|
|
311
|
+
streamWriter.close().catch(() => {})
|
|
312
|
+
}
|
|
313
|
+
if (connection) {
|
|
314
|
+
connection.close().catch(() => {})
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
connection = await kernel.sockets.createWebTransport(url)
|
|
322
|
+
|
|
323
|
+
if (interrupted) {
|
|
324
|
+
await connection.close()
|
|
325
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
326
|
+
|
|
327
|
+
terminal.listen()
|
|
328
|
+
terminal.write('\n')
|
|
329
|
+
|
|
330
|
+
return 1
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const transport = connection.transport
|
|
334
|
+
const bidirectionalStream = await transport.createBidirectionalStream()
|
|
335
|
+
|
|
336
|
+
if (interrupted) {
|
|
337
|
+
await connection.close()
|
|
338
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
339
|
+
|
|
340
|
+
terminal.listen()
|
|
341
|
+
terminal.write('\n')
|
|
342
|
+
|
|
343
|
+
return 1
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
streamReader = bidirectionalStream.readable.getReader()
|
|
347
|
+
streamWriter = bidirectionalStream.writable.getWriter()
|
|
348
|
+
|
|
349
|
+
stdoutWriter = process.stdout.getWriter()
|
|
350
|
+
|
|
351
|
+
terminal.clearCommand()
|
|
352
|
+
terminal.unlisten()
|
|
353
|
+
|
|
354
|
+
const encoder = new TextEncoder()
|
|
355
|
+
|
|
356
|
+
keyListener = terminal.onKey(async ({ domEvent }) => {
|
|
357
|
+
if (interrupted || !streamWriter) return
|
|
358
|
+
|
|
359
|
+
const key = domEvent.key
|
|
360
|
+
|
|
361
|
+
if (domEvent.ctrlKey && key === 'c') {
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
domEvent.preventDefault()
|
|
366
|
+
domEvent.stopPropagation()
|
|
367
|
+
|
|
368
|
+
if (key === 'Enter') {
|
|
369
|
+
try {
|
|
370
|
+
await streamWriter.write(encoder.encode('\n'))
|
|
371
|
+
terminal.write('\n')
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (!interrupted) {
|
|
374
|
+
await writelnStderr(process, terminal, `Error sending data: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else if (key.length === 1) {
|
|
378
|
+
try {
|
|
379
|
+
await streamWriter.write(encoder.encode(key))
|
|
380
|
+
terminal.write(key)
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (!interrupted) {
|
|
383
|
+
await writelnStderr(process, terminal, `Error sending data: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
const readStream = async () => {
|
|
390
|
+
try {
|
|
391
|
+
while (!interrupted) {
|
|
392
|
+
const { done, value } = await streamReader!.read()
|
|
393
|
+
if (done || interrupted) break
|
|
394
|
+
if (stdoutWriter && value && !interrupted) {
|
|
395
|
+
await stdoutWriter.write(value)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (!interrupted) {
|
|
400
|
+
await writelnStderr(process, terminal, `Error reading stream: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
401
|
+
}
|
|
402
|
+
} finally {
|
|
403
|
+
if (streamReader) {
|
|
404
|
+
streamReader.releaseLock()
|
|
405
|
+
}
|
|
406
|
+
if (stdoutWriter) {
|
|
407
|
+
stdoutWriter.releaseLock()
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await readStream()
|
|
413
|
+
|
|
414
|
+
if (keyListener) {
|
|
415
|
+
keyListener.dispose()
|
|
416
|
+
keyListener = null
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (streamWriter) {
|
|
420
|
+
try {
|
|
421
|
+
await streamWriter.close()
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
424
|
+
streamWriter.releaseLock()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (streamReader) {
|
|
428
|
+
streamReader.releaseLock()
|
|
429
|
+
}
|
|
430
|
+
if (stdoutWriter) {
|
|
431
|
+
stdoutWriter.releaseLock()
|
|
432
|
+
}
|
|
433
|
+
if (connection) {
|
|
434
|
+
await connection.close()
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
438
|
+
|
|
439
|
+
terminal.listen()
|
|
440
|
+
terminal.write('\n')
|
|
441
|
+
|
|
442
|
+
return interrupted ? 1 : 0
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (!interrupted) {
|
|
445
|
+
await writelnStderr(process, terminal, `WebTransport error: ${error instanceof Error ? error.message : 'Connection failed'}`)
|
|
446
|
+
}
|
|
447
|
+
if (connection) {
|
|
448
|
+
await connection.close()
|
|
449
|
+
}
|
|
450
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
451
|
+
|
|
452
|
+
terminal.listen()
|
|
453
|
+
terminal.write('\n')
|
|
454
|
+
|
|
455
|
+
return 1
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
460
|
+
return new TerminalCommand({
|
|
461
|
+
command: 'nc',
|
|
462
|
+
description: 'Netcat - network utility for reading from and writing to network connections',
|
|
463
|
+
kernel,
|
|
464
|
+
shell,
|
|
465
|
+
terminal,
|
|
466
|
+
run: async (pid: number, argv: string[]) => {
|
|
467
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
468
|
+
|
|
469
|
+
if (!process) return 1
|
|
470
|
+
|
|
471
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
472
|
+
printUsage(process, terminal)
|
|
473
|
+
return 0
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (argv.length === 0) {
|
|
477
|
+
await writelnStderr(process, terminal, 'nc: missing host or URL argument')
|
|
478
|
+
await writelnStderr(process, terminal, 'Try "nc --help" for more information.')
|
|
479
|
+
return 1
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const connectionOptions = parseUrl(argv)
|
|
483
|
+
if (!connectionOptions) {
|
|
484
|
+
await writelnStderr(process, terminal, 'nc: invalid arguments')
|
|
485
|
+
await writelnStderr(process, terminal, 'Try "nc --help" for more information.')
|
|
486
|
+
return 1
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (connectionOptions.useWebTransport) {
|
|
490
|
+
return await connectWebTransport(connectionOptions.url, process, kernel, terminal)
|
|
491
|
+
} else if (connectionOptions.useWebSocket) {
|
|
492
|
+
return await connectWebSocket(connectionOptions.url, process, kernel, terminal)
|
|
493
|
+
} else {
|
|
494
|
+
await writelnStderr(process, terminal, 'nc: unsupported URL scheme. Use ws://, wss://, or https://')
|
|
495
|
+
return 1
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
})
|
|
499
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
|
+
import { TerminalEvents } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: nl [OPTION]... [FILE]...
|
|
9
|
+
Number lines of files.
|
|
10
|
+
|
|
11
|
+
-v, --starting-line=NUMBER first line number for each section (default: 1)
|
|
12
|
+
-i, --increment=NUMBER line number increment at each line (default: 1)
|
|
13
|
+
-n, --format=FORMAT line number format: ln, rn, rz (default: rn)
|
|
14
|
+
-w, --width=NUMBER use NUMBER columns for line numbers (default: 6)
|
|
15
|
+
-s, --separator=STRING add STRING after (possible) line number (default: TAB)
|
|
16
|
+
--help display this help and exit`
|
|
17
|
+
writelnStderr(process, terminal, usage)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
21
|
+
return new TerminalCommand({
|
|
22
|
+
command: 'nl',
|
|
23
|
+
description: 'Number lines of files',
|
|
24
|
+
kernel,
|
|
25
|
+
shell,
|
|
26
|
+
terminal,
|
|
27
|
+
run: async (pid: number, argv: string[]) => {
|
|
28
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
29
|
+
|
|
30
|
+
if (!process) return 1
|
|
31
|
+
|
|
32
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
33
|
+
printUsage(process, terminal)
|
|
34
|
+
return 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const files: string[] = []
|
|
38
|
+
let startLine = 1
|
|
39
|
+
let increment = 1
|
|
40
|
+
let format = 'rn'
|
|
41
|
+
let width = 6
|
|
42
|
+
let separator = '\t'
|
|
43
|
+
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const arg = argv[i]
|
|
46
|
+
if (!arg) continue
|
|
47
|
+
|
|
48
|
+
if (arg === '--help' || arg === '-h') {
|
|
49
|
+
printUsage(process, terminal)
|
|
50
|
+
return 0
|
|
51
|
+
} else if (arg === '-v' || arg === '--starting-line') {
|
|
52
|
+
if (i + 1 < argv.length) {
|
|
53
|
+
const nextArg = argv[++i]
|
|
54
|
+
if (nextArg !== undefined) {
|
|
55
|
+
const num = parseInt(nextArg, 10)
|
|
56
|
+
if (!isNaN(num)) startLine = num
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} else if (arg.startsWith('--starting-line=')) {
|
|
60
|
+
const num = parseInt(arg.slice(16), 10)
|
|
61
|
+
if (!isNaN(num)) startLine = num
|
|
62
|
+
} else if (arg === '-i' || arg === '--increment') {
|
|
63
|
+
if (i + 1 < argv.length) {
|
|
64
|
+
const nextArg = argv[++i]
|
|
65
|
+
if (nextArg !== undefined) {
|
|
66
|
+
const num = parseInt(nextArg, 10)
|
|
67
|
+
if (!isNaN(num)) increment = num
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} else if (arg.startsWith('--increment=')) {
|
|
71
|
+
const num = parseInt(arg.slice(12), 10)
|
|
72
|
+
if (!isNaN(num)) increment = num
|
|
73
|
+
} else if (arg === '-n' || arg === '--format') {
|
|
74
|
+
if (i + 1 < argv.length) {
|
|
75
|
+
format = argv[++i] || 'rn'
|
|
76
|
+
}
|
|
77
|
+
} else if (arg.startsWith('--format=')) {
|
|
78
|
+
format = arg.slice(9) || 'rn'
|
|
79
|
+
} else if (arg === '-w' || arg === '--width') {
|
|
80
|
+
if (i + 1 < argv.length) {
|
|
81
|
+
const nextArg = argv[++i]
|
|
82
|
+
if (nextArg !== undefined) {
|
|
83
|
+
const num = parseInt(nextArg, 10)
|
|
84
|
+
if (!isNaN(num)) width = num
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (arg.startsWith('--width=')) {
|
|
88
|
+
const num = parseInt(arg.slice(8), 10)
|
|
89
|
+
if (!isNaN(num)) width = num
|
|
90
|
+
} else if (arg === '-s' || arg === '--separator') {
|
|
91
|
+
if (i + 1 < argv.length) {
|
|
92
|
+
separator = argv[++i] || '\t'
|
|
93
|
+
}
|
|
94
|
+
} else if (arg.startsWith('--separator=')) {
|
|
95
|
+
separator = arg.slice(12) || '\t'
|
|
96
|
+
} else if (!arg.startsWith('-')) {
|
|
97
|
+
files.push(arg)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const writer = process.stdout.getWriter()
|
|
102
|
+
|
|
103
|
+
const formatNumber = (num: number): string => {
|
|
104
|
+
const numStr = num.toString()
|
|
105
|
+
if (format === 'rz') {
|
|
106
|
+
return numStr.padStart(width, '0')
|
|
107
|
+
} else if (format === 'ln') {
|
|
108
|
+
return numStr.padEnd(width, ' ')
|
|
109
|
+
} else {
|
|
110
|
+
return numStr.padStart(width, ' ')
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
let lines: string[] = []
|
|
116
|
+
|
|
117
|
+
if (files.length === 0) {
|
|
118
|
+
if (!process.stdin) {
|
|
119
|
+
return 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const reader = process.stdin.getReader()
|
|
123
|
+
const decoder = new TextDecoder()
|
|
124
|
+
let buffer = ''
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
while (true) {
|
|
128
|
+
const { done, value } = await reader.read()
|
|
129
|
+
if (done) break
|
|
130
|
+
if (value) {
|
|
131
|
+
buffer += decoder.decode(value, { stream: true })
|
|
132
|
+
const newLines = buffer.split('\n')
|
|
133
|
+
buffer = newLines.pop() || ''
|
|
134
|
+
lines.push(...newLines)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (buffer) {
|
|
138
|
+
lines.push(buffer)
|
|
139
|
+
}
|
|
140
|
+
} finally {
|
|
141
|
+
reader.releaseLock()
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const fullPath = path.resolve(shell.cwd, file)
|
|
146
|
+
|
|
147
|
+
let interrupted = false
|
|
148
|
+
const interruptHandler = () => { interrupted = true }
|
|
149
|
+
kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
if (fullPath.startsWith('/dev')) {
|
|
153
|
+
await writelnStderr(process, terminal, `nl: ${file}: cannot number device files`)
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const handle = await shell.context.fs.promises.open(fullPath, 'r')
|
|
158
|
+
const stat = await shell.context.fs.promises.stat(fullPath)
|
|
159
|
+
|
|
160
|
+
const decoder = new TextDecoder()
|
|
161
|
+
let content = ''
|
|
162
|
+
let bytesRead = 0
|
|
163
|
+
const chunkSize = 1024
|
|
164
|
+
|
|
165
|
+
while (bytesRead < stat.size) {
|
|
166
|
+
if (interrupted) break
|
|
167
|
+
const data = new Uint8Array(chunkSize)
|
|
168
|
+
const readSize = Math.min(chunkSize, stat.size - bytesRead)
|
|
169
|
+
await handle.read(data, 0, readSize, bytesRead)
|
|
170
|
+
const chunk = data.subarray(0, readSize)
|
|
171
|
+
content += decoder.decode(chunk, { stream: true })
|
|
172
|
+
bytesRead += readSize
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const fileLines = content.split('\n')
|
|
176
|
+
if (fileLines[fileLines.length - 1] === '') {
|
|
177
|
+
fileLines.pop()
|
|
178
|
+
}
|
|
179
|
+
lines.push(...fileLines)
|
|
180
|
+
} catch (error) {
|
|
181
|
+
await writelnStderr(process, terminal, `nl: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
182
|
+
} finally {
|
|
183
|
+
kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let lineNumber = startLine
|
|
189
|
+
for (const line of lines) {
|
|
190
|
+
const formattedNum = formatNumber(lineNumber)
|
|
191
|
+
await writer.write(new TextEncoder().encode(`${formattedNum}${separator}${line}\n`))
|
|
192
|
+
lineNumber += increment
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return 0
|
|
196
|
+
} finally {
|
|
197
|
+
writer.releaseLock()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
}
|