@ecmaos/coreutils 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/LICENSE +1 -1
  4. package/dist/commands/cal.js +2 -2
  5. package/dist/commands/cal.js.map +1 -1
  6. package/dist/commands/cat.js +2 -2
  7. package/dist/commands/cd.js +2 -2
  8. package/dist/commands/chmod.d.ts.map +1 -1
  9. package/dist/commands/chmod.js +16 -11
  10. package/dist/commands/chmod.js.map +1 -1
  11. package/dist/commands/cp.js +2 -2
  12. package/dist/commands/cp.js.map +1 -1
  13. package/dist/commands/date.js +2 -2
  14. package/dist/commands/date.js.map +1 -1
  15. package/dist/commands/echo.js +2 -2
  16. package/dist/commands/echo.js.map +1 -1
  17. package/dist/commands/false.js +2 -2
  18. package/dist/commands/fetch.d.ts +4 -0
  19. package/dist/commands/fetch.d.ts.map +1 -0
  20. package/dist/commands/fetch.js +210 -0
  21. package/dist/commands/fetch.js.map +1 -0
  22. package/dist/commands/format.d.ts +4 -0
  23. package/dist/commands/format.d.ts.map +1 -0
  24. package/dist/commands/format.js +178 -0
  25. package/dist/commands/format.js.map +1 -0
  26. package/dist/commands/hash.d.ts +4 -0
  27. package/dist/commands/hash.d.ts.map +1 -0
  28. package/dist/commands/hash.js +200 -0
  29. package/dist/commands/hash.js.map +1 -0
  30. package/dist/commands/head.js +2 -2
  31. package/dist/commands/id.js +2 -2
  32. package/dist/commands/id.js.map +1 -1
  33. package/dist/commands/less.d.ts.map +1 -1
  34. package/dist/commands/less.js +53 -2
  35. package/dist/commands/less.js.map +1 -1
  36. package/dist/commands/ls.d.ts.map +1 -1
  37. package/dist/commands/ls.js +41 -15
  38. package/dist/commands/ls.js.map +1 -1
  39. package/dist/commands/man.d.ts +4 -0
  40. package/dist/commands/man.d.ts.map +1 -0
  41. package/dist/commands/man.js +554 -0
  42. package/dist/commands/man.js.map +1 -0
  43. package/dist/commands/mkdir.js +2 -2
  44. package/dist/commands/mkdir.js.map +1 -1
  45. package/dist/commands/mktemp.d.ts +4 -0
  46. package/dist/commands/mktemp.d.ts.map +1 -0
  47. package/dist/commands/mktemp.js +229 -0
  48. package/dist/commands/mktemp.js.map +1 -0
  49. package/dist/commands/nc.js +2 -2
  50. package/dist/commands/nc.js.map +1 -1
  51. package/dist/commands/passkey.js +3 -3
  52. package/dist/commands/pwd.js +2 -2
  53. package/dist/commands/pwd.js.map +1 -1
  54. package/dist/commands/rm.d.ts.map +1 -1
  55. package/dist/commands/rm.js +57 -12
  56. package/dist/commands/rm.js.map +1 -1
  57. package/dist/commands/rmdir.js +2 -2
  58. package/dist/commands/rmdir.js.map +1 -1
  59. package/dist/commands/sockets.js +1 -1
  60. package/dist/commands/stat.d.ts.map +1 -1
  61. package/dist/commands/stat.js +37 -15
  62. package/dist/commands/stat.js.map +1 -1
  63. package/dist/commands/tail.js +2 -2
  64. package/dist/commands/tar.d.ts +4 -0
  65. package/dist/commands/tar.d.ts.map +1 -0
  66. package/dist/commands/tar.js +693 -0
  67. package/dist/commands/tar.js.map +1 -0
  68. package/dist/commands/touch.js +2 -2
  69. package/dist/commands/touch.js.map +1 -1
  70. package/dist/commands/true.js +2 -2
  71. package/dist/commands/unzip.d.ts +4 -0
  72. package/dist/commands/unzip.d.ts.map +1 -0
  73. package/dist/commands/unzip.js +443 -0
  74. package/dist/commands/unzip.js.map +1 -0
  75. package/dist/commands/user.js +1 -1
  76. package/dist/commands/whoami.js +2 -2
  77. package/dist/commands/whoami.js.map +1 -1
  78. package/dist/commands/zip.d.ts +4 -0
  79. package/dist/commands/zip.d.ts.map +1 -0
  80. package/dist/commands/zip.js +264 -0
  81. package/dist/commands/zip.js.map +1 -0
  82. package/dist/index.d.ts +8 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +25 -1
  85. package/dist/index.js.map +1 -1
  86. package/package.json +6 -4
  87. package/src/commands/cal.ts +2 -2
  88. package/src/commands/cat.ts +2 -2
  89. package/src/commands/cd.ts +2 -2
  90. package/src/commands/chmod.ts +19 -11
  91. package/src/commands/cp.ts +2 -2
  92. package/src/commands/date.ts +2 -2
  93. package/src/commands/echo.ts +2 -2
  94. package/src/commands/false.ts +2 -2
  95. package/src/commands/fetch.ts +205 -0
  96. package/src/commands/format.ts +204 -0
  97. package/src/commands/hash.ts +215 -0
  98. package/src/commands/head.ts +2 -2
  99. package/src/commands/id.ts +2 -2
  100. package/src/commands/less.ts +50 -2
  101. package/src/commands/ls.ts +40 -14
  102. package/src/commands/man.ts +643 -0
  103. package/src/commands/mkdir.ts +2 -2
  104. package/src/commands/mktemp.ts +235 -0
  105. package/src/commands/nc.ts +2 -2
  106. package/src/commands/passkey.ts +3 -3
  107. package/src/commands/pwd.ts +2 -2
  108. package/src/commands/rm.ts +54 -12
  109. package/src/commands/rmdir.ts +2 -2
  110. package/src/commands/sockets.ts +1 -1
  111. package/src/commands/stat.ts +44 -16
  112. package/src/commands/tail.ts +2 -2
  113. package/src/commands/tar.ts +737 -0
  114. package/src/commands/touch.ts +2 -2
  115. package/src/commands/true.ts +2 -2
  116. package/src/commands/unzip.ts +517 -0
  117. package/src/commands/user.ts +1 -1
  118. package/src/commands/whoami.ts +2 -2
  119. package/src/commands/zip.ts +319 -0
  120. package/src/index.ts +25 -1
@@ -0,0 +1,215 @@
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
+ type HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'
8
+
9
+ const SUPPORTED_ALGORITHMS: Record<string, HashAlgorithm> = {
10
+ 'sha1': 'SHA-1',
11
+ 'sha-1': 'SHA-1',
12
+ 'sha256': 'SHA-256',
13
+ 'sha-256': 'SHA-256',
14
+ 'sha384': 'SHA-384',
15
+ 'sha-384': 'SHA-384',
16
+ 'sha512': 'SHA-512',
17
+ 'sha-512': 'SHA-512'
18
+ }
19
+
20
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
21
+ const usage = `Usage: hash [OPTION]... [FILE]...
22
+ Compute and display hash values for files or standard input.
23
+
24
+ -a, --algorithm=ALGORITHM hash algorithm to use (sha1, sha256, sha384, sha512)
25
+ default: sha256
26
+ --help display this help and exit
27
+
28
+ Supported algorithms:
29
+ sha1, sha-1 SHA-1 (160 bits)
30
+ sha256, sha-256 SHA-256 (256 bits) [default]
31
+ sha384, sha-384 SHA-384 (384 bits)
32
+ sha512, sha-512 SHA-512 (512 bits)
33
+
34
+ Examples:
35
+ hash file.txt compute SHA-256 hash of file.txt
36
+ hash -a sha512 file.txt compute SHA-512 hash of file.txt
37
+ echo "hello" | hash compute SHA-256 hash of stdin`
38
+ writelnStderr(process, terminal, usage)
39
+ }
40
+
41
+ async function hashData(data: Uint8Array, algorithm: HashAlgorithm): Promise<string> {
42
+ // Create a new Uint8Array with a proper ArrayBuffer to ensure compatibility
43
+ const dataCopy = new Uint8Array(data)
44
+ const hashBuffer = await crypto.subtle.digest(algorithm, dataCopy)
45
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
46
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
47
+ }
48
+
49
+ async function readStreamToUint8Array(reader: ReadableStreamDefaultReader<Uint8Array>): Promise<Uint8Array> {
50
+ const chunks: Uint8Array[] = []
51
+ try {
52
+ while (true) {
53
+ const { done, value } = await reader.read()
54
+ if (done) break
55
+ if (value) {
56
+ chunks.push(value)
57
+ }
58
+ }
59
+ } finally {
60
+ reader.releaseLock()
61
+ }
62
+
63
+ // Calculate total length
64
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
65
+
66
+ // Concatenate all chunks into a single Uint8Array
67
+ const result = new Uint8Array(totalLength)
68
+ let offset = 0
69
+ for (const chunk of chunks) {
70
+ result.set(chunk, offset)
71
+ offset += chunk.length
72
+ }
73
+
74
+ return result
75
+ }
76
+
77
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
78
+ return new TerminalCommand({
79
+ command: 'hash',
80
+ description: 'Compute and display hash values for files or standard input',
81
+ kernel,
82
+ shell,
83
+ terminal,
84
+ run: async (pid: number, argv: string[]) => {
85
+ const process = kernel.processes.get(pid) as Process | undefined
86
+
87
+ if (!process) return 1
88
+
89
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
90
+ printUsage(process, terminal)
91
+ return 0
92
+ }
93
+
94
+ let algorithm: HashAlgorithm = 'SHA-256'
95
+ const files: string[] = []
96
+
97
+ // Parse arguments
98
+ for (let i = 0; i < argv.length; i++) {
99
+ const arg = argv[i]
100
+ if (arg === undefined) continue
101
+
102
+ if (arg === '--help' || arg === '-h') {
103
+ printUsage(process, terminal)
104
+ return 0
105
+ } else if (arg === '-a' || arg === '--algorithm') {
106
+ const algoArg = argv[i + 1]
107
+ if (!algoArg) {
108
+ await writelnStderr(process, terminal, `hash: option requires an argument -- '${arg === '-a' ? 'a' : 'algorithm'}'`)
109
+ return 1
110
+ }
111
+ const algoLower = algoArg.toLowerCase()
112
+ const selectedAlgorithm = SUPPORTED_ALGORITHMS[algoLower]
113
+ if (!selectedAlgorithm) {
114
+ await writelnStderr(process, terminal, `hash: unsupported algorithm '${algoArg}'\nSupported algorithms: ${Object.keys(SUPPORTED_ALGORITHMS).join(', ')}`)
115
+ return 1
116
+ }
117
+ algorithm = selectedAlgorithm
118
+ i++ // Skip the next argument as it's the algorithm value
119
+ } else if (arg.startsWith('--algorithm=')) {
120
+ const algoArg = arg.split('=')[1]
121
+ if (!algoArg) {
122
+ await writelnStderr(process, terminal, `hash: option requires an argument -- 'algorithm'`)
123
+ return 1
124
+ }
125
+ const algoLower = algoArg.toLowerCase()
126
+ const selectedAlgorithm = SUPPORTED_ALGORITHMS[algoLower]
127
+ if (!selectedAlgorithm) {
128
+ await writelnStderr(process, terminal, `hash: unsupported algorithm '${algoArg}'\nSupported algorithms: ${Object.keys(SUPPORTED_ALGORITHMS).join(', ')}`)
129
+ return 1
130
+ }
131
+ algorithm = selectedAlgorithm
132
+ } else if (!arg.startsWith('-')) {
133
+ files.push(arg)
134
+ } else {
135
+ await writelnStderr(process, terminal, `hash: invalid option -- '${arg.replace(/^-+/, '')}'`)
136
+ await writelnStderr(process, terminal, `Try 'hash --help' for more information.`)
137
+ return 1
138
+ }
139
+ }
140
+
141
+ const writer = process.stdout.getWriter()
142
+
143
+ try {
144
+ // If no files specified, read from stdin
145
+ if (files.length === 0) {
146
+ if (!process.stdin) {
147
+ await writelnStderr(process, terminal, 'hash: no input specified')
148
+ return 1
149
+ }
150
+
151
+ const reader = process.stdin.getReader()
152
+ const data = await readStreamToUint8Array(reader)
153
+ const hash = await hashData(data, algorithm)
154
+ await writer.write(new TextEncoder().encode(hash + '\n'))
155
+ return 0
156
+ }
157
+
158
+ // Process each file
159
+ let hasError = false
160
+ for (const file of files) {
161
+ const fullPath = path.resolve(shell.cwd, file)
162
+
163
+ let interrupted = false
164
+ const interruptHandler = () => { interrupted = true }
165
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
166
+
167
+ try {
168
+ if (fullPath.startsWith('/dev')) {
169
+ await writelnStderr(process, terminal, `hash: ${file}: cannot hash device files`)
170
+ hasError = true
171
+ continue
172
+ }
173
+
174
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
175
+ const stat = await shell.context.fs.promises.stat(fullPath)
176
+
177
+ const chunks: Uint8Array[] = []
178
+ let bytesRead = 0
179
+ const chunkSize = 64 * 1024 // 64KB chunks for better performance
180
+
181
+ while (bytesRead < stat.size) {
182
+ if (interrupted) break
183
+ const data = new Uint8Array(chunkSize)
184
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
185
+ await handle.read(data, 0, readSize, bytesRead)
186
+ chunks.push(data.subarray(0, readSize))
187
+ bytesRead += readSize
188
+ }
189
+
190
+ // Concatenate all chunks
191
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0)
192
+ const fileData = new Uint8Array(totalLength)
193
+ let offset = 0
194
+ for (const chunk of chunks) {
195
+ fileData.set(chunk, offset)
196
+ offset += chunk.length
197
+ }
198
+
199
+ const hash = await hashData(fileData, algorithm)
200
+ await writer.write(new TextEncoder().encode(`${hash} ${file}\n`))
201
+ } catch (error) {
202
+ await writelnStderr(process, terminal, `hash: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
203
+ hasError = true
204
+ } finally {
205
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
206
+ }
207
+ }
208
+
209
+ return hasError ? 1 : 0
210
+ } finally {
211
+ writer.releaseLock()
212
+ }
213
+ }
214
+ })
215
+ }
@@ -2,7 +2,7 @@ import path from 'path'
2
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
3
  import { TerminalEvents } from '@ecmaos/types'
4
4
  import { TerminalCommand } from '../shared/terminal-command.js'
5
- import { writelnStdout } from '../shared/helpers.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
6
 
7
7
  function printUsage(process: Process | undefined, terminal: Terminal): void {
8
8
  const usage = `Usage: head [OPTION]... [FILE]...
@@ -10,7 +10,7 @@ Print the first 10 lines of each FILE to standard output.
10
10
 
11
11
  -n, -nNUMBER print the first NUMBER lines instead of 10
12
12
  --help display this help and exit`
13
- writelnStdout(process, terminal, usage)
13
+ writelnStderr(process, terminal, usage)
14
14
  }
15
15
 
16
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -1,6 +1,6 @@
1
1
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
2
  import { TerminalCommand } from '../shared/terminal-command.js'
3
- import { writelnStdout } from '../shared/helpers.js'
3
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
4
4
 
5
5
  function printUsage(process: Process | undefined, terminal: Terminal): void {
6
6
  const usage = `Usage: id [OPTION]...
@@ -11,7 +11,7 @@ Print user and group IDs.
11
11
  -G, --groups print all group IDs
12
12
  -n, --name print names instead of numeric IDs
13
13
  --help display this help and exit`
14
- writelnStdout(process, terminal, usage)
14
+ writelnStderr(process, terminal, usage)
15
15
  }
16
16
 
17
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -33,6 +33,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
33
33
 
34
34
  let lines: string[] = []
35
35
  let currentLine = 0
36
+ let horizontalOffset = 0
36
37
  let keyListener: IDisposable | null = null
37
38
  let linesRendered = 0
38
39
 
@@ -94,6 +95,40 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
94
95
  const displayRows = rows - 1
95
96
 
96
97
  const render = () => {
98
+ const cols = terminal.cols
99
+ const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
100
+
101
+ const getVisibleSlice = (line: string, offset: number): string => {
102
+ const visibleLen = stripAnsi(line).length
103
+
104
+ if (offset < 0) offset = 0
105
+ if (offset > visibleLen) offset = visibleLen
106
+
107
+ let visible = 0
108
+ let result = ''
109
+ let inEscape = false
110
+ let charsSkipped = 0
111
+
112
+ for (const char of line) {
113
+ if (char === '\x1b') inEscape = true
114
+ if (inEscape) {
115
+ if (charsSkipped >= offset) {
116
+ result += char
117
+ }
118
+ if (/[a-zA-Z]/.test(char)) inEscape = false
119
+ } else {
120
+ if (charsSkipped < offset) {
121
+ charsSkipped++
122
+ } else {
123
+ if (visible >= cols) break
124
+ result += char
125
+ visible++
126
+ }
127
+ }
128
+ }
129
+ return result
130
+ }
131
+
97
132
  const maxLine = Math.max(0, lines.length - displayRows)
98
133
  if (currentLine > maxLine) {
99
134
  currentLine = maxLine
@@ -102,6 +137,11 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
102
137
  currentLine = 0
103
138
  }
104
139
 
140
+ const maxLineLength = Math.max(...lines.map(l => stripAnsi(l).length), 0)
141
+ const maxHorizontalOffset = Math.max(0, maxLineLength - cols)
142
+ if (horizontalOffset > maxHorizontalOffset) horizontalOffset = maxHorizontalOffset
143
+ if (horizontalOffset < 0) horizontalOffset = 0
144
+
105
145
  if (linesRendered > 0) {
106
146
  terminal.write(ansi.cursor.up(linesRendered))
107
147
  }
@@ -111,7 +151,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
111
151
 
112
152
  for (let i = currentLine; i < endLine; i++) {
113
153
  terminal.write(ansi.erase.inLine(2))
114
- const line = lines[i] || ''
154
+ const line = getVisibleSlice(lines[i] || '', horizontalOffset)
115
155
  terminal.write(line)
116
156
  linesRendered++
117
157
  if (i < endLine - 1) {
@@ -129,7 +169,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
129
169
  const statusLine = `-- ${currentLine + 1}-${endLine} / ${lines.length} (${percentage}%)`
130
170
  terminal.write('\n')
131
171
  terminal.write(ansi.erase.inLine(2))
132
- terminal.write(statusLine)
172
+ terminal.write(getVisibleSlice(statusLine, 0))
133
173
  linesRendered++
134
174
  }
135
175
 
@@ -163,6 +203,14 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
163
203
  currentLine++
164
204
  render()
165
205
  break
206
+ case 'ArrowLeft':
207
+ horizontalOffset = Math.max(0, horizontalOffset - Math.floor(terminal.cols / 2))
208
+ render()
209
+ break
210
+ case 'ArrowRight':
211
+ horizontalOffset += Math.floor(terminal.cols / 2)
212
+ render()
213
+ break
166
214
  case 'PageDown':
167
215
  case ' ':
168
216
  currentLine = Math.min(currentLine + displayRows, Math.max(0, lines.length - displayRows))
@@ -4,14 +4,14 @@ import columnify from 'columnify'
4
4
  import humanFormat from 'human-format'
5
5
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
6
6
  import { TerminalCommand } from '../shared/terminal-command.js'
7
- import { writelnStdout } from '../shared/helpers.js'
7
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
8
8
 
9
9
  function printUsage(process: Process | undefined, terminal: Terminal): void {
10
10
  const usage = `Usage: ls [OPTION]... [FILE]...
11
11
  List information about the FILEs (the current directory by default).
12
12
 
13
13
  --help display this help and exit`
14
- writelnStdout(process, terminal, usage)
14
+ writelnStderr(process, terminal, usage)
15
15
  }
16
16
 
17
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
@@ -29,12 +29,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
29
29
  return 0
30
30
  }
31
31
 
32
- const target = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : shell.cwd
33
- const fullPath = target ? path.resolve(shell.cwd, target === '' ? '.' : target) : shell.cwd
34
- const stats = await shell.context.fs.promises.stat(fullPath)
35
- const entries: string[] = stats.isDirectory() ? await shell.context.fs.promises.readdir(fullPath) : [fullPath]
32
+ // Filter out options/flags and get target paths
33
+ const targets = argv.length > 0
34
+ ? argv.filter(arg => !arg.startsWith('-'))
35
+ : [shell.cwd]
36
+
37
+ if (targets.length === 0) targets.push(shell.cwd)
38
+
36
39
  const descriptions = kernel.filesystem.descriptions(kernel.i18n.t)
37
40
 
41
+ // Process each target and collect all entries
42
+ // We'll determine if each entry is a directory when we stat it later
43
+ const allEntries: Array<{ fullPath: string, entry: string }> = []
44
+
45
+ for (const target of targets) {
46
+ const fullPath = path.resolve(shell.cwd, target === '' ? '.' : target)
47
+ try {
48
+ const stats = await shell.context.fs.promises.stat(fullPath)
49
+ if (stats.isDirectory()) {
50
+ // For directories, list all contents
51
+ const dirEntries = await shell.context.fs.promises.readdir(fullPath)
52
+ for (const entry of dirEntries) allEntries.push({ fullPath, entry })
53
+ } else {
54
+ // For files, add the file itself
55
+ allEntries.push({ fullPath: path.dirname(fullPath), entry: path.basename(fullPath) })
56
+ }
57
+ } catch {
58
+ // If target doesn't exist, skip it (standard ls behavior)
59
+ continue
60
+ }
61
+ }
62
+
38
63
  const getModeType = (stats: Awaited<ReturnType<typeof shell.context.fs.promises.stat>>) => {
39
64
  let type = '-'
40
65
  if (stats.isDirectory()) type = 'd'
@@ -85,9 +110,9 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
85
110
  return ''
86
111
  }
87
112
 
88
- const filesMap = await Promise.all(entries
89
- .map(async entry => {
90
- const target = path.resolve(fullPath, entry)
113
+ const filesMap = await Promise.all(allEntries
114
+ .map(async ({ fullPath: entryFullPath, entry }) => {
115
+ const target = path.resolve(entryFullPath, entry)
91
116
  try {
92
117
  let linkTarget: string | null = null
93
118
  let linkStats = null
@@ -119,9 +144,9 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
119
144
  .filter(entry => entry && entry.stats && !entry.stats.isDirectory())
120
145
  .filter((entry): entry is NonNullable<typeof entry> => entry !== null && entry !== undefined)
121
146
 
122
- const directoryMap = await Promise.all(entries
123
- .map(async entry => {
124
- const target = path.resolve(fullPath, entry)
147
+ const directoryMap = await Promise.all(allEntries
148
+ .map(async ({ fullPath: entryFullPath, entry }) => {
149
+ const target = path.resolve(entryFullPath, entry)
125
150
  try {
126
151
  let linkTarget: string | null = null
127
152
  let linkStats = null
@@ -154,7 +179,8 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
154
179
  .filter((entry, index, self) => self.findIndex(e => e?.name === entry?.name) === index)
155
180
  .filter((entry): entry is NonNullable<typeof entry> => entry !== null && entry !== undefined)
156
181
 
157
- const isDevDirectory = fullPath.startsWith('/dev')
182
+ // Check if any entry is in /dev directory
183
+ const isDevDirectory = allEntries.some(e => e.fullPath.startsWith('/dev'))
158
184
  const columns = isDevDirectory ? ['Name', 'Mode', 'Owner', 'Info'] : ['Name', 'Size', 'Modified', 'Mode', 'Owner', 'Info']
159
185
 
160
186
  const directoryRows = directories.sort((a, b) => a.name.localeCompare(b.name)).map(directory => {
@@ -209,7 +235,7 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
209
235
  const linkInfo = getLinkInfo(file.linkTarget, file.linkStats, file.stats)
210
236
  if (linkInfo) return linkInfo
211
237
 
212
- if (descriptions.has(path.resolve(fullPath, file.name))) return descriptions.get(path.resolve(fullPath, file.name)) || ''
238
+ if (descriptions.has(file.target)) return descriptions.get(file.target) || ''
213
239
  if (file.name.includes('.')) {
214
240
  const ext = file.name.split('.').pop()
215
241
  if (ext && descriptions.has('.' + ext)) return descriptions.get('.' + ext) || ''