@ecmaos/coreutils 0.1.4 → 0.2.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +55 -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 +49 -22
- 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 +51 -9
- 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 +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +175 -0
- package/dist/commands/grep.js.map +1 -0
- package/dist/commands/head.d.ts +4 -0
- package/dist/commands/head.d.ts.map +1 -0
- package/dist/commands/head.js +199 -0
- package/dist/commands/head.js.map +1 -0
- package/dist/commands/hex.d.ts.map +1 -1
- package/dist/commands/hex.js +82 -20
- 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 +19 -9
- 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 +4 -0
- package/dist/commands/tail.d.ts.map +1 -0
- package/dist/commands/tail.js +189 -0
- package/dist/commands/tail.js.map +1 -0
- 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/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 +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -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 +55 -24
- 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 +53 -10
- package/src/commands/false.ts +31 -0
- package/src/commands/find.ts +184 -0
- package/src/commands/grep.ts +187 -0
- package/src/commands/head.ts +206 -0
- package/src/commands/hex.ts +93 -25
- 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 +21 -10
- 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 +200 -0
- 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/wc.ts +181 -0
- package/src/commands/which.ts +89 -0
- package/src/commands/whoami.ts +32 -0
- package/src/index.ts +72 -1
- package/src/shared/terminal-command.ts +43 -16
package/src/commands/rmdir.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type {
|
|
3
|
-
import type { Kernel, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
+
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
5
|
+
|
|
6
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
|
+
const usage = `Usage: rmdir [OPTION]... DIRECTORY...
|
|
8
|
+
Remove the DIRECTORY(ies), if they are empty.
|
|
9
|
+
|
|
10
|
+
--help display this help and exit`
|
|
11
|
+
writelnStdout(process, terminal, usage)
|
|
12
|
+
}
|
|
5
13
|
|
|
6
14
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
7
15
|
return new TerminalCommand({
|
|
@@ -10,16 +18,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
10
18
|
kernel,
|
|
11
19
|
shell,
|
|
12
20
|
terminal,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
run: async (pid: number, argv: string[]) => {
|
|
22
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
23
|
+
|
|
24
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
25
|
+
printUsage(process, terminal)
|
|
26
|
+
return 0
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (argv.length === 0) {
|
|
30
|
+
await writelnStderr(process, terminal, 'rmdir: missing operand')
|
|
31
|
+
await writelnStderr(process, terminal, "Try 'rmdir --help' for more information.")
|
|
32
|
+
return 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let hasError = false
|
|
36
|
+
|
|
37
|
+
for (const target of argv) {
|
|
38
|
+
if (!target || target.startsWith('-')) continue
|
|
39
|
+
|
|
40
|
+
const fullPath = target ? path.resolve(shell.cwd, target) : shell.cwd
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
await shell.context.fs.promises.rm(fullPath, { recursive: true, force: true })
|
|
44
|
+
} catch (error) {
|
|
45
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
46
|
+
await writelnStderr(process, terminal, `rmdir: ${target}: ${errorMessage}`)
|
|
47
|
+
hasError = true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return hasError ? 1 : 0
|
|
22
52
|
}
|
|
23
53
|
})
|
|
24
54
|
}
|
|
25
|
-
|
package/src/commands/sed.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type { CommandLineOptions } from 'command-line-args'
|
|
3
2
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
4
|
import { writelnStderr } from '../shared/helpers.js'
|
|
@@ -16,6 +15,19 @@ interface SedCommand {
|
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
19
|
+
const usage = `Usage: sed [OPTION]... {script-only-if-no-other-script} [input-file]...
|
|
20
|
+
|
|
21
|
+
Stream editor for filtering and transforming text.
|
|
22
|
+
|
|
23
|
+
-e, --expression=script add the script to the commands to be executed
|
|
24
|
+
-f, --file=script-file add the contents of script-file to the commands
|
|
25
|
+
-i[SUFFIX], --in-place[=SUFFIX] edit files in place (makes backup if SUFFIX supplied)
|
|
26
|
+
-q, --quiet suppress normal output
|
|
27
|
+
--help display this help and exit`
|
|
28
|
+
writelnStderr(process, terminal, usage)
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
function parseSedExpression(expr: string): SedCommand | null {
|
|
20
32
|
expr = expr.trim()
|
|
21
33
|
|
|
@@ -203,21 +215,21 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
203
215
|
kernel,
|
|
204
216
|
shell,
|
|
205
217
|
terminal,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{ name: 'file', type: String, alias: 'f', description: 'Add the contents of script-file to the commands to be executed' },
|
|
210
|
-
{ name: 'inplace', type: String, alias: 'i', description: 'Edit files in place (makes backup if extension supplied)' },
|
|
211
|
-
{ name: 'quiet', type: Boolean, alias: 'q', description: 'Suppress normal output' },
|
|
212
|
-
{ name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'Expression or input file(s)' }
|
|
213
|
-
],
|
|
214
|
-
run: async (argv: CommandLineOptions, process?: Process) => {
|
|
218
|
+
run: async (pid: number, argv: string[]) => {
|
|
219
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
220
|
+
|
|
215
221
|
if (!process) return 1
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
224
|
+
printUsage(process, terminal)
|
|
225
|
+
return 0
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let expressions: string[] = []
|
|
229
|
+
const files: string[] = []
|
|
230
|
+
let scriptFile: string | undefined
|
|
231
|
+
let inplace: string | undefined
|
|
232
|
+
let quiet = false
|
|
221
233
|
|
|
222
234
|
const isSedExpression = (arg: string): boolean => {
|
|
223
235
|
if (!arg) return false
|
|
@@ -232,34 +244,56 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
232
244
|
)
|
|
233
245
|
}
|
|
234
246
|
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
for (let i = 0; i < argv.length; i++) {
|
|
248
|
+
const arg = argv[i]
|
|
249
|
+
if (!arg) continue
|
|
237
250
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} else {
|
|
242
|
-
|
|
251
|
+
if (arg === '--help' || arg === '-h') {
|
|
252
|
+
printUsage(process, terminal)
|
|
253
|
+
return 0
|
|
254
|
+
} else if (arg === '-e' || arg === '--expression') {
|
|
255
|
+
if (i + 1 < argv.length) {
|
|
256
|
+
expressions.push(argv[++i] || '')
|
|
257
|
+
}
|
|
258
|
+
} else if (arg.startsWith('--expression=')) {
|
|
259
|
+
expressions.push(arg.slice(13))
|
|
260
|
+
} else if (arg.startsWith('-e')) {
|
|
261
|
+
expressions.push(arg.slice(2))
|
|
262
|
+
} else if (arg === '-f' || arg === '--file') {
|
|
263
|
+
if (i + 1 < argv.length) {
|
|
264
|
+
scriptFile = argv[++i]
|
|
265
|
+
}
|
|
266
|
+
} else if (arg.startsWith('--file=')) {
|
|
267
|
+
scriptFile = arg.slice(7)
|
|
268
|
+
} else if (arg.startsWith('-f')) {
|
|
269
|
+
scriptFile = arg.slice(2)
|
|
270
|
+
} else if (arg === '-i' || arg === '--in-place') {
|
|
271
|
+
inplace = ''
|
|
272
|
+
} else if (arg.startsWith('--in-place=')) {
|
|
273
|
+
inplace = arg.slice(12)
|
|
274
|
+
} else if (arg.startsWith('-i')) {
|
|
275
|
+
inplace = arg.slice(2) || ''
|
|
276
|
+
} else if (arg === '-q' || arg === '--quiet') {
|
|
277
|
+
quiet = true
|
|
278
|
+
} else if (isSedExpression(arg)) {
|
|
279
|
+
expressions.push(arg)
|
|
280
|
+
} else if (!arg.startsWith('-')) {
|
|
281
|
+
files.push(arg)
|
|
243
282
|
}
|
|
244
283
|
}
|
|
245
284
|
|
|
246
|
-
if (
|
|
247
|
-
expressions = [...expressions, ...potentialExpressions]
|
|
248
|
-
files = potentialFiles
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (expressions.length === 0 && !argv.file) {
|
|
285
|
+
if (expressions.length === 0 && !scriptFile) {
|
|
252
286
|
await writelnStderr(process, terminal, 'sed: No expression provided')
|
|
253
287
|
return 1
|
|
254
288
|
}
|
|
255
289
|
|
|
256
290
|
const commands: SedCommand[] = []
|
|
257
291
|
|
|
258
|
-
if (
|
|
259
|
-
const scriptPath = path.resolve(shell.cwd,
|
|
292
|
+
if (scriptFile) {
|
|
293
|
+
const scriptPath = path.resolve(shell.cwd, scriptFile)
|
|
260
294
|
const exists = await shell.context.fs.promises.exists(scriptPath)
|
|
261
295
|
if (!exists) {
|
|
262
|
-
await writelnStderr(process, terminal, `sed: ${
|
|
296
|
+
await writelnStderr(process, terminal, `sed: ${scriptFile}: No such file or directory`)
|
|
263
297
|
return 1
|
|
264
298
|
}
|
|
265
299
|
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
4
|
+
|
|
5
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
|
+
const usage = `Usage: seq [OPTION]... LAST
|
|
7
|
+
seq [OPTION]... FIRST LAST
|
|
8
|
+
seq [OPTION]... FIRST INCREMENT LAST
|
|
9
|
+
Print numbers from FIRST to LAST, in steps of INCREMENT.
|
|
10
|
+
|
|
11
|
+
-s, --separator=STRING use STRING to separate numbers (default: \\n)
|
|
12
|
+
--help display this help and exit`
|
|
13
|
+
writelnStderr(process, terminal, usage)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
17
|
+
return new TerminalCommand({
|
|
18
|
+
command: 'seq',
|
|
19
|
+
description: 'Print a sequence of numbers',
|
|
20
|
+
kernel,
|
|
21
|
+
shell,
|
|
22
|
+
terminal,
|
|
23
|
+
run: async (pid: number, argv: string[]) => {
|
|
24
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
25
|
+
|
|
26
|
+
if (!process) return 1
|
|
27
|
+
|
|
28
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
29
|
+
printUsage(process, terminal)
|
|
30
|
+
return 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let separator = '\n'
|
|
34
|
+
const args: string[] = []
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < argv.length; i++) {
|
|
37
|
+
const arg = argv[i]
|
|
38
|
+
if (!arg) continue
|
|
39
|
+
if (arg === '--help' || arg === '-h') {
|
|
40
|
+
printUsage(process, terminal)
|
|
41
|
+
return 0
|
|
42
|
+
} else if (arg === '-s' || arg === '--separator') {
|
|
43
|
+
if (i + 1 < argv.length) {
|
|
44
|
+
const nextArg = argv[++i]
|
|
45
|
+
if (nextArg !== undefined) {
|
|
46
|
+
separator = nextArg
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
await writelnStderr(process, terminal, 'seq: option requires an argument -- \'s\'')
|
|
50
|
+
return 1
|
|
51
|
+
}
|
|
52
|
+
} else if (arg.startsWith('--separator=')) {
|
|
53
|
+
separator = arg.slice(12)
|
|
54
|
+
} else if (arg.startsWith('-s')) {
|
|
55
|
+
separator = arg.slice(2)
|
|
56
|
+
} else if (!arg.startsWith('-')) {
|
|
57
|
+
args.push(arg)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (args.length === 0) {
|
|
62
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
63
|
+
return 1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let first = 1
|
|
67
|
+
let increment = 1
|
|
68
|
+
let last: number
|
|
69
|
+
|
|
70
|
+
if (args.length === 1) {
|
|
71
|
+
const arg0 = args[0]
|
|
72
|
+
if (arg0 === undefined) {
|
|
73
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
76
|
+
last = parseFloat(arg0)
|
|
77
|
+
if (isNaN(last)) {
|
|
78
|
+
await writelnStderr(process, terminal, `seq: invalid number: ${arg0}`)
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
} else if (args.length === 2) {
|
|
82
|
+
const arg0 = args[0]
|
|
83
|
+
const arg1 = args[1]
|
|
84
|
+
if (arg0 === undefined || arg1 === undefined) {
|
|
85
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
86
|
+
return 1
|
|
87
|
+
}
|
|
88
|
+
first = parseFloat(arg0)
|
|
89
|
+
last = parseFloat(arg1)
|
|
90
|
+
if (isNaN(first) || isNaN(last)) {
|
|
91
|
+
await writelnStderr(process, terminal, 'seq: invalid number')
|
|
92
|
+
return 1
|
|
93
|
+
}
|
|
94
|
+
} else if (args.length === 3) {
|
|
95
|
+
const arg0 = args[0]
|
|
96
|
+
const arg1 = args[1]
|
|
97
|
+
const arg2 = args[2]
|
|
98
|
+
if (arg0 === undefined || arg1 === undefined || arg2 === undefined) {
|
|
99
|
+
await writelnStderr(process, terminal, 'seq: missing operand')
|
|
100
|
+
return 1
|
|
101
|
+
}
|
|
102
|
+
first = parseFloat(arg0)
|
|
103
|
+
increment = parseFloat(arg1)
|
|
104
|
+
last = parseFloat(arg2)
|
|
105
|
+
if (isNaN(first) || isNaN(increment) || isNaN(last)) {
|
|
106
|
+
await writelnStderr(process, terminal, 'seq: invalid number')
|
|
107
|
+
return 1
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
await writelnStderr(process, terminal, 'seq: too many arguments')
|
|
111
|
+
return 1
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const writer = process.stdout.getWriter()
|
|
115
|
+
const numbers: number[] = []
|
|
116
|
+
|
|
117
|
+
if (increment > 0) {
|
|
118
|
+
for (let i = first; i <= last; i += increment) {
|
|
119
|
+
numbers.push(i)
|
|
120
|
+
}
|
|
121
|
+
} else if (increment < 0) {
|
|
122
|
+
for (let i = first; i >= last; i += increment) {
|
|
123
|
+
numbers.push(i)
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
await writelnStderr(process, terminal, 'seq: zero increment')
|
|
127
|
+
writer.releaseLock()
|
|
128
|
+
return 1
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const output = numbers.map(n => {
|
|
132
|
+
if (Number.isInteger(n)) {
|
|
133
|
+
return n.toString()
|
|
134
|
+
} else {
|
|
135
|
+
return n.toFixed(10).replace(/\.?0+$/, '')
|
|
136
|
+
}
|
|
137
|
+
}).join(separator)
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await writer.write(new TextEncoder().encode(output + (separator === '\n' ? '' : '\n')))
|
|
141
|
+
return 0
|
|
142
|
+
} finally {
|
|
143
|
+
writer.releaseLock()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import columnify from 'columnify'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal, SocketConnection } from '@ecmaos/types'
|
|
4
|
+
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
|
+
import { writelnStdout, writelnStderr } from '../shared/helpers.js'
|
|
6
|
+
|
|
7
|
+
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
8
|
+
const usage = `Usage: sockets [COMMAND] [OPTIONS]
|
|
9
|
+
|
|
10
|
+
Manage socket connections (WebSocket and WebTransport).
|
|
11
|
+
|
|
12
|
+
Commands:
|
|
13
|
+
list, ls List all active connections
|
|
14
|
+
create, c <url> Create a new connection
|
|
15
|
+
close, d <id> Close a connection by ID
|
|
16
|
+
show, s <id> Show detailed information about a connection
|
|
17
|
+
--help, -h Display this help and exit
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-t, --type <type> Connection type: websocket or webtransport (for create)
|
|
21
|
+
-p, --protocols WebSocket protocols (comma-separated, for create)
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
sockets list
|
|
25
|
+
sockets create wss://echo.websocket.org
|
|
26
|
+
sockets create https://example.com:443 -t webtransport
|
|
27
|
+
sockets close abc-123-def-456
|
|
28
|
+
sockets show abc-123-def-456`
|
|
29
|
+
writelnStdout(process, terminal, usage)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findConnectionById(kernel: Kernel, id: string): SocketConnection | undefined {
|
|
33
|
+
const fullMatch = kernel.sockets.get(id)
|
|
34
|
+
if (fullMatch) return fullMatch
|
|
35
|
+
|
|
36
|
+
const allConnections = kernel.sockets.all()
|
|
37
|
+
for (const [fullId, conn] of allConnections.entries()) {
|
|
38
|
+
if (fullId.startsWith(id) || fullId.substring(0, 8) === id) {
|
|
39
|
+
return conn
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function listConnections(
|
|
46
|
+
process: Process | undefined,
|
|
47
|
+
kernel: Kernel,
|
|
48
|
+
terminal: Terminal
|
|
49
|
+
): Promise<number> {
|
|
50
|
+
const allConnections = kernel.sockets.all()
|
|
51
|
+
const connections = Array.from(allConnections.values())
|
|
52
|
+
|
|
53
|
+
if (connections.length === 0) {
|
|
54
|
+
await writelnStdout(process, terminal, 'No active connections.')
|
|
55
|
+
return 0
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const data = connections.map((conn, index) => {
|
|
59
|
+
const id = conn.id.substring(0, 8)
|
|
60
|
+
const type = conn.type === 'websocket' ? chalk.cyan('WS') : chalk.magenta('WT')
|
|
61
|
+
const state = conn.state === 'open' ? chalk.green(conn.state) :
|
|
62
|
+
conn.state === 'connecting' ? chalk.yellow(conn.state) :
|
|
63
|
+
conn.state === 'closing' ? chalk.yellow(conn.state) :
|
|
64
|
+
chalk.gray(conn.state)
|
|
65
|
+
const age = Math.floor((Date.now() - conn.created) / 1000)
|
|
66
|
+
const ageStr = age < 60 ? `${age}s` : age < 3600 ? `${Math.floor(age / 60)}m` : `${Math.floor(age / 3600)}h`
|
|
67
|
+
const url = conn.url.length > 50 ? conn.url.substring(0, 47) + '...' : conn.url
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
'#': `${index + 1}`,
|
|
71
|
+
ID: chalk.bold(id),
|
|
72
|
+
TYPE: type,
|
|
73
|
+
STATE: state,
|
|
74
|
+
AGE: chalk.gray(ageStr),
|
|
75
|
+
URL: url
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const table = columnify(data, {
|
|
80
|
+
columns: ['#', 'ID', 'TYPE', 'STATE', 'AGE', 'URL'],
|
|
81
|
+
columnSplitter: ' ',
|
|
82
|
+
config: {
|
|
83
|
+
'#': { maxWidth: 4 },
|
|
84
|
+
ID: { maxWidth: 10 },
|
|
85
|
+
TYPE: { maxWidth: 4 },
|
|
86
|
+
STATE: { maxWidth: 10 },
|
|
87
|
+
AGE: { maxWidth: 4 }
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
await writelnStdout(process, terminal, table)
|
|
92
|
+
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function showConnection(
|
|
97
|
+
process: Process | undefined,
|
|
98
|
+
kernel: Kernel,
|
|
99
|
+
terminal: Terminal,
|
|
100
|
+
id: string
|
|
101
|
+
): Promise<number> {
|
|
102
|
+
const connection = findConnectionById(kernel, id)
|
|
103
|
+
|
|
104
|
+
if (!connection) {
|
|
105
|
+
await writelnStderr(process, terminal, `sockets: connection not found: ${id}`)
|
|
106
|
+
return 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const age = Math.floor((Date.now() - connection.created) / 1000)
|
|
110
|
+
const ageSeconds = age
|
|
111
|
+
const ageMinutes = Math.floor(age / 60)
|
|
112
|
+
const ageHours = Math.floor(age / 3600)
|
|
113
|
+
const ageStr = age < 60 ? `${ageSeconds} seconds` :
|
|
114
|
+
age < 3600 ? `${ageMinutes} minutes, ${ageSeconds % 60} seconds` :
|
|
115
|
+
`${ageHours} hours, ${Math.floor((age % 3600) / 60)} minutes`
|
|
116
|
+
|
|
117
|
+
writelnStdout(process, terminal, chalk.bold('Connection Details'))
|
|
118
|
+
writelnStdout(process, terminal, chalk.gray('─'.repeat(40)))
|
|
119
|
+
writelnStdout(process, terminal, `ID: ${chalk.bold(connection.id)}`)
|
|
120
|
+
writelnStdout(process, terminal, `Type: ${connection.type === 'websocket' ? chalk.cyan('WebSocket') : chalk.magenta('WebTransport')}`)
|
|
121
|
+
writelnStdout(process, terminal, `State: ${connection.state === 'open' ? chalk.green(connection.state) :
|
|
122
|
+
connection.state === 'connecting' ? chalk.yellow(connection.state) :
|
|
123
|
+
connection.state === 'closing' ? chalk.yellow(connection.state) :
|
|
124
|
+
chalk.gray(connection.state)}`)
|
|
125
|
+
writelnStdout(process, terminal, `URL: ${connection.url}`)
|
|
126
|
+
writelnStdout(process, terminal, `Created: ${new Date(connection.created).toISOString()}`)
|
|
127
|
+
writelnStdout(process, terminal, `Age: ${ageStr}`)
|
|
128
|
+
|
|
129
|
+
if (connection.type === 'websocket') {
|
|
130
|
+
const ws = connection.socket
|
|
131
|
+
writelnStdout(process, terminal, `Protocol: ${ws.protocol || '(none)'}`)
|
|
132
|
+
writelnStdout(process, terminal, `Extensions: ${ws.extensions || '(none)'}`)
|
|
133
|
+
writelnStdout(process, terminal, `BinaryType: ${ws.binaryType}`)
|
|
134
|
+
writelnStdout(process, terminal, `ReadyState: ${ws.readyState} (${connection.state})`)
|
|
135
|
+
} else {
|
|
136
|
+
writelnStdout(process, terminal, `State: ${connection.state}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return 0
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function createConnection(
|
|
143
|
+
process: Process | undefined,
|
|
144
|
+
kernel: Kernel,
|
|
145
|
+
terminal: Terminal,
|
|
146
|
+
url: string,
|
|
147
|
+
type?: string,
|
|
148
|
+
protocols?: string
|
|
149
|
+
): Promise<number> {
|
|
150
|
+
try {
|
|
151
|
+
let connection: SocketConnection
|
|
152
|
+
|
|
153
|
+
if (type === 'webtransport' || (!type && url.startsWith('https://'))) {
|
|
154
|
+
if (!('WebTransport' in globalThis)) {
|
|
155
|
+
await writelnStderr(process, terminal, 'sockets: WebTransport is not supported in this browser')
|
|
156
|
+
return 1
|
|
157
|
+
}
|
|
158
|
+
connection = await kernel.sockets.createWebTransport(url)
|
|
159
|
+
await writelnStdout(process, terminal, `Created WebTransport connection: ${chalk.bold(connection.id.substring(0, 8))}`)
|
|
160
|
+
} else if (type === 'websocket' || url.startsWith('ws://') || url.startsWith('wss://')) {
|
|
161
|
+
const options = protocols ? { protocols: protocols.split(',') } : undefined
|
|
162
|
+
connection = await kernel.sockets.createWebSocket(url, options)
|
|
163
|
+
await writelnStdout(process, terminal, `Created WebSocket connection: ${chalk.bold(connection.id.substring(0, 8))}`)
|
|
164
|
+
} else {
|
|
165
|
+
await writelnStderr(process, terminal, 'sockets: unable to determine connection type. Use -t to specify type or use a URL with ws://, wss://, or https:// scheme')
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await writelnStdout(process, terminal, `URL: ${connection.url}`)
|
|
170
|
+
await writelnStdout(process, terminal, `State: ${connection.state === 'open' ? chalk.green(connection.state) : chalk.yellow(connection.state)}`)
|
|
171
|
+
|
|
172
|
+
return 0
|
|
173
|
+
} catch (error) {
|
|
174
|
+
await writelnStderr(process, terminal, `sockets: failed to create connection: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
175
|
+
return 1
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function closeConnection(
|
|
180
|
+
process: Process | undefined,
|
|
181
|
+
kernel: Kernel,
|
|
182
|
+
terminal: Terminal,
|
|
183
|
+
id: string
|
|
184
|
+
): Promise<number> {
|
|
185
|
+
const connection = findConnectionById(kernel, id)
|
|
186
|
+
|
|
187
|
+
if (!connection) {
|
|
188
|
+
await writelnStderr(process, terminal, `sockets: connection not found: ${id}`)
|
|
189
|
+
return 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await kernel.sockets.close(connection.id)
|
|
194
|
+
await writelnStdout(process, terminal, `Closed connection: ${chalk.bold(connection.id.substring(0, 8))}`)
|
|
195
|
+
return 0
|
|
196
|
+
} catch (error) {
|
|
197
|
+
await writelnStderr(process, terminal, `sockets: failed to close connection: ${error instanceof Error ? error.message : 'Unknown error'}`)
|
|
198
|
+
return 1
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
203
|
+
return new TerminalCommand({
|
|
204
|
+
command: 'sockets',
|
|
205
|
+
description: 'Manage socket connections (WebSocket and WebTransport)',
|
|
206
|
+
kernel,
|
|
207
|
+
shell,
|
|
208
|
+
terminal,
|
|
209
|
+
run: async (pid: number, argv: string[]) => {
|
|
210
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
211
|
+
|
|
212
|
+
if (!process) return 1
|
|
213
|
+
|
|
214
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
215
|
+
printUsage(process, terminal)
|
|
216
|
+
return 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (argv.length === 0 || argv[0] === 'list' || argv[0] === 'ls') {
|
|
220
|
+
return await listConnections(process, kernel, terminal)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const command = argv[0]
|
|
224
|
+
let type: string | undefined
|
|
225
|
+
let protocols: string | undefined
|
|
226
|
+
|
|
227
|
+
for (let i = 1; i < argv.length; i++) {
|
|
228
|
+
const arg = argv[i]
|
|
229
|
+
if (arg === '-t' || arg === '--type') {
|
|
230
|
+
if (i + 1 < argv.length) {
|
|
231
|
+
i++
|
|
232
|
+
type = argv[i]
|
|
233
|
+
} else {
|
|
234
|
+
await writelnStderr(process, terminal, 'sockets: --type requires a value')
|
|
235
|
+
return 1
|
|
236
|
+
}
|
|
237
|
+
} else if (arg === '-p' || arg === '--protocols') {
|
|
238
|
+
if (i + 1 < argv.length) {
|
|
239
|
+
i++
|
|
240
|
+
protocols = argv[i]
|
|
241
|
+
} else {
|
|
242
|
+
await writelnStderr(process, terminal, 'sockets: --protocols requires a value')
|
|
243
|
+
return 1
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (command === 'create' || command === 'c') {
|
|
249
|
+
if (argv.length < 2 || !argv[1] || argv[1].startsWith('-')) {
|
|
250
|
+
await writelnStderr(process, terminal, 'sockets: create requires a URL')
|
|
251
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
252
|
+
return 1
|
|
253
|
+
}
|
|
254
|
+
const url = argv[1]
|
|
255
|
+
return await createConnection(process, kernel, terminal, url, type, protocols)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (command === 'close' || command === 'd') {
|
|
259
|
+
if (argv.length < 2 || !argv[1] || argv[1].startsWith('-')) {
|
|
260
|
+
await writelnStderr(process, terminal, 'sockets: close requires a connection ID')
|
|
261
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
262
|
+
return 1
|
|
263
|
+
}
|
|
264
|
+
const id = argv[1]
|
|
265
|
+
return await closeConnection(process, kernel, terminal, id)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (command === 'show' || command === 's') {
|
|
269
|
+
if (argv.length < 2 || !argv[1] || argv[1].startsWith('-')) {
|
|
270
|
+
await writelnStderr(process, terminal, 'sockets: show requires a connection ID')
|
|
271
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
272
|
+
return 1
|
|
273
|
+
}
|
|
274
|
+
const id = argv[1]
|
|
275
|
+
return await showConnection(process, kernel, terminal, id)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await writelnStderr(process, terminal, `sockets: unknown command: ${command}`)
|
|
279
|
+
await writelnStderr(process, terminal, 'Try "sockets --help" for more information.')
|
|
280
|
+
return 1
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
}
|