@ecmaos/coreutils 0.2.0 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +25 -0
- package/LICENSE +1 -1
- package/dist/commands/cal.js +2 -2
- package/dist/commands/cal.js.map +1 -1
- package/dist/commands/cat.js +2 -2
- package/dist/commands/cd.js +2 -2
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +16 -11
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/cp.js +2 -2
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/date.js +2 -2
- package/dist/commands/date.js.map +1 -1
- package/dist/commands/echo.js +2 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/false.js +2 -2
- package/dist/commands/fetch.d.ts +4 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +210 -0
- package/dist/commands/fetch.js.map +1 -0
- package/dist/commands/format.d.ts +4 -0
- package/dist/commands/format.d.ts.map +1 -0
- package/dist/commands/format.js +178 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/hash.d.ts +4 -0
- package/dist/commands/hash.d.ts.map +1 -0
- package/dist/commands/hash.js +200 -0
- package/dist/commands/hash.js.map +1 -0
- package/dist/commands/head.js +2 -2
- package/dist/commands/id.js +2 -2
- package/dist/commands/id.js.map +1 -1
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +53 -2
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/ls.d.ts.map +1 -1
- package/dist/commands/ls.js +120 -97
- package/dist/commands/ls.js.map +1 -1
- package/dist/commands/man.d.ts +4 -0
- package/dist/commands/man.d.ts.map +1 -0
- package/dist/commands/man.js +554 -0
- package/dist/commands/man.js.map +1 -0
- package/dist/commands/mkdir.js +2 -2
- package/dist/commands/mkdir.js.map +1 -1
- package/dist/commands/mktemp.d.ts +4 -0
- package/dist/commands/mktemp.d.ts.map +1 -0
- package/dist/commands/mktemp.js +229 -0
- package/dist/commands/mktemp.js.map +1 -0
- package/dist/commands/nc.js +2 -2
- package/dist/commands/nc.js.map +1 -1
- package/dist/commands/passkey.js +3 -3
- package/dist/commands/pwd.js +2 -2
- package/dist/commands/pwd.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +57 -12
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/rmdir.js +2 -2
- package/dist/commands/rmdir.js.map +1 -1
- package/dist/commands/sockets.js +1 -1
- package/dist/commands/stat.d.ts.map +1 -1
- package/dist/commands/stat.js +37 -15
- package/dist/commands/stat.js.map +1 -1
- package/dist/commands/tail.js +2 -2
- package/dist/commands/tar.d.ts +4 -0
- package/dist/commands/tar.d.ts.map +1 -0
- package/dist/commands/tar.js +693 -0
- package/dist/commands/tar.js.map +1 -0
- package/dist/commands/touch.js +2 -2
- package/dist/commands/touch.js.map +1 -1
- package/dist/commands/true.js +2 -2
- package/dist/commands/unzip.d.ts +4 -0
- package/dist/commands/unzip.d.ts.map +1 -0
- package/dist/commands/unzip.js +443 -0
- package/dist/commands/unzip.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/whoami.js +2 -2
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/zip.d.ts +4 -0
- package/dist/commands/zip.d.ts.map +1 -0
- package/dist/commands/zip.js +264 -0
- package/dist/commands/zip.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
- package/src/commands/cal.ts +2 -2
- package/src/commands/cat.ts +2 -2
- package/src/commands/cd.ts +2 -2
- package/src/commands/chmod.ts +19 -11
- package/src/commands/cp.ts +2 -2
- package/src/commands/date.ts +2 -2
- package/src/commands/echo.ts +2 -2
- package/src/commands/false.ts +2 -2
- package/src/commands/fetch.ts +205 -0
- package/src/commands/format.ts +204 -0
- package/src/commands/hash.ts +215 -0
- package/src/commands/head.ts +2 -2
- package/src/commands/id.ts +2 -2
- package/src/commands/less.ts +50 -2
- package/src/commands/ls.ts +131 -91
- package/src/commands/man.ts +643 -0
- package/src/commands/mkdir.ts +2 -2
- package/src/commands/mktemp.ts +235 -0
- package/src/commands/nc.ts +2 -2
- package/src/commands/passkey.ts +3 -3
- package/src/commands/pwd.ts +2 -2
- package/src/commands/rm.ts +54 -12
- package/src/commands/rmdir.ts +2 -2
- package/src/commands/sockets.ts +1 -1
- package/src/commands/stat.ts +44 -16
- package/src/commands/tail.ts +2 -2
- package/src/commands/tar.ts +737 -0
- package/src/commands/touch.ts +2 -2
- package/src/commands/true.ts +2 -2
- package/src/commands/unzip.ts +517 -0
- package/src/commands/user.ts +436 -0
- package/src/commands/whoami.ts +2 -2
- package/src/commands/zip.ts +319 -0
- package/src/index.ts +28 -1
package/src/commands/chmod.ts
CHANGED
|
@@ -40,22 +40,30 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
40
40
|
return 1
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const mode = args[0]
|
|
44
|
+
const targets = args.slice(1)
|
|
45
|
+
|
|
46
|
+
if (!mode || targets.length === 0) {
|
|
45
47
|
await writelnStderr(process, terminal, chalk.red('chmod: missing operand'))
|
|
48
|
+
await writelnStderr(process, terminal, 'Try \'chmod --help\' for more information.')
|
|
46
49
|
return 1
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
let hasError = false
|
|
53
|
+
|
|
54
|
+
for (const target of targets) {
|
|
55
|
+
const fullPath = path.resolve(shell.cwd, target)
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await shell.context.fs.promises.chmod(fullPath, mode)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
61
|
+
await writelnStderr(process, terminal, `chmod: ${target}: ${errorMessage}`)
|
|
62
|
+
hasError = true
|
|
63
|
+
}
|
|
58
64
|
}
|
|
65
|
+
|
|
66
|
+
return hasError ? 1 : 0
|
|
59
67
|
}
|
|
60
68
|
})
|
|
61
69
|
}
|
package/src/commands/cp.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
3
3
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
4
|
-
import {
|
|
4
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
5
5
|
|
|
6
6
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
7
7
|
const usage = `Usage: cp [OPTION]... SOURCE... DEST
|
|
8
8
|
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
|
|
9
9
|
|
|
10
10
|
--help display this help and exit`
|
|
11
|
-
|
|
11
|
+
writelnStderr(process, terminal, usage)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
package/src/commands/date.ts
CHANGED
|
@@ -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: date [OPTION]... [+FORMAT]
|
|
@@ -10,7 +10,7 @@ Print or set the system date and time.
|
|
|
10
10
|
-R, --rfc-2822 output date and time in RFC 2822 format
|
|
11
11
|
-f, --format=FORMAT output date/time in specified format (strftime-like)
|
|
12
12
|
--help display this help and exit`
|
|
13
|
-
|
|
13
|
+
writelnStderr(process, terminal, usage)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
package/src/commands/echo.ts
CHANGED
|
@@ -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: echo [OPTION]... [STRING]...
|
|
@@ -8,7 +8,7 @@ Echo the STRING(s) to standard output.
|
|
|
8
8
|
|
|
9
9
|
-n do not output the trailing newline
|
|
10
10
|
--help display this help and exit`
|
|
11
|
-
|
|
11
|
+
writelnStderr(process, terminal, usage)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
package/src/commands/false.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
2
2
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
3
|
-
import {
|
|
3
|
+
import { writelnStderr } from '../shared/helpers.js'
|
|
4
4
|
|
|
5
5
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
6
6
|
const usage = `Usage: false
|
|
7
7
|
Return an unsuccessful exit status.
|
|
8
8
|
|
|
9
9
|
--help display this help and exit`
|
|
10
|
-
|
|
10
|
+
writelnStderr(process, terminal, usage)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import type { Kernel, Process, Shell, Terminal } 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: fetch [OPTION]... URL
|
|
9
|
+
Fetch a resource from the network.
|
|
10
|
+
|
|
11
|
+
-o, --output=FILE write output to FILE instead of stdout
|
|
12
|
+
-X, --method=METHOD HTTP method to use (default: GET)
|
|
13
|
+
-d, --data=DATA request body data to send
|
|
14
|
+
-H, --header=HEADER add custom HTTP header (format: "Name: Value")
|
|
15
|
+
--help display this help and exit
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
fetch https://example.com fetch and output to stdout
|
|
19
|
+
fetch -o file.txt https://example.com fetch and save to file.txt
|
|
20
|
+
fetch -X POST -d "data" https://api.example.com POST request with body`
|
|
21
|
+
writelnStderr(process, terminal, usage)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
25
|
+
return new TerminalCommand({
|
|
26
|
+
command: 'fetch',
|
|
27
|
+
description: 'Fetch a resource from the network',
|
|
28
|
+
kernel,
|
|
29
|
+
shell,
|
|
30
|
+
terminal,
|
|
31
|
+
run: async (pid: number, argv: string[]) => {
|
|
32
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
33
|
+
|
|
34
|
+
if (!process) return 1
|
|
35
|
+
|
|
36
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
37
|
+
printUsage(process, terminal)
|
|
38
|
+
return 0
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let url: string | undefined
|
|
42
|
+
let outputFile: string | undefined
|
|
43
|
+
let method = 'GET'
|
|
44
|
+
let body: string | undefined
|
|
45
|
+
const headers: Record<string, string> = {}
|
|
46
|
+
|
|
47
|
+
// Parse arguments
|
|
48
|
+
for (let i = 0; i < argv.length; i++) {
|
|
49
|
+
const arg = argv[i]
|
|
50
|
+
if (arg === undefined) continue
|
|
51
|
+
|
|
52
|
+
if (arg === '--help' || arg === '-h') {
|
|
53
|
+
printUsage(process, terminal)
|
|
54
|
+
return 0
|
|
55
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
56
|
+
const fileArg = argv[i + 1]
|
|
57
|
+
if (!fileArg) {
|
|
58
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-o' ? 'o' : 'output'}'`)
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
outputFile = fileArg
|
|
62
|
+
i++ // Skip the next argument as it's the file value
|
|
63
|
+
} else if (arg.startsWith('--output=')) {
|
|
64
|
+
const outputValue = arg.split('=')[1]
|
|
65
|
+
if (!outputValue) {
|
|
66
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- 'output'`)
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
outputFile = outputValue
|
|
70
|
+
} else if (arg === '-X' || arg === '--method') {
|
|
71
|
+
const methodArg = argv[i + 1]
|
|
72
|
+
if (!methodArg) {
|
|
73
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-X' ? 'X' : 'method'}'`)
|
|
74
|
+
return 1
|
|
75
|
+
}
|
|
76
|
+
method = methodArg.toUpperCase()
|
|
77
|
+
i++ // Skip the next argument as it's the method value
|
|
78
|
+
} else if (arg.startsWith('--method=')) {
|
|
79
|
+
const methodValue = arg.split('=')[1]
|
|
80
|
+
if (!methodValue) {
|
|
81
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- 'method'`)
|
|
82
|
+
return 1
|
|
83
|
+
}
|
|
84
|
+
method = methodValue.toUpperCase()
|
|
85
|
+
} else if (arg === '-d' || arg === '--data') {
|
|
86
|
+
const dataArg = argv[i + 1]
|
|
87
|
+
if (!dataArg) {
|
|
88
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-d' ? 'd' : 'data'}'`)
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
body = dataArg
|
|
92
|
+
i++ // Skip the next argument as it's the data value
|
|
93
|
+
} else if (arg.startsWith('--data=')) {
|
|
94
|
+
const dataValue = arg.split('=')[1]
|
|
95
|
+
if (!dataValue) {
|
|
96
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- 'data'`)
|
|
97
|
+
return 1
|
|
98
|
+
}
|
|
99
|
+
body = dataValue
|
|
100
|
+
} else if (arg === '-H' || arg === '--header') {
|
|
101
|
+
const headerArg = argv[i + 1]
|
|
102
|
+
if (!headerArg) {
|
|
103
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- '${arg === '-H' ? 'H' : 'header'}'`)
|
|
104
|
+
return 1
|
|
105
|
+
}
|
|
106
|
+
const [name, ...valueParts] = headerArg.split(':')
|
|
107
|
+
if (!name || valueParts.length === 0) {
|
|
108
|
+
await writelnStderr(process, terminal, `fetch: invalid header format. Expected "Name: Value"`)
|
|
109
|
+
return 1
|
|
110
|
+
}
|
|
111
|
+
headers[name.trim()] = valueParts.join(':').trim()
|
|
112
|
+
i++ // Skip the next argument as it's the header value
|
|
113
|
+
} else if (arg.startsWith('--header=')) {
|
|
114
|
+
const headerValue = arg.split('=')[1]
|
|
115
|
+
if (!headerValue) {
|
|
116
|
+
await writelnStderr(process, terminal, `fetch: option requires an argument -- 'header'`)
|
|
117
|
+
return 1
|
|
118
|
+
}
|
|
119
|
+
const [name, ...valueParts] = headerValue.split(':')
|
|
120
|
+
if (!name || valueParts.length === 0) {
|
|
121
|
+
await writelnStderr(process, terminal, `fetch: invalid header format. Expected "Name: Value"`)
|
|
122
|
+
return 1
|
|
123
|
+
}
|
|
124
|
+
headers[name.trim()] = valueParts.join(':').trim()
|
|
125
|
+
} else if (!arg.startsWith('-')) {
|
|
126
|
+
// Positional argument - should be the URL
|
|
127
|
+
if (!url) {
|
|
128
|
+
url = arg
|
|
129
|
+
} else {
|
|
130
|
+
await writelnStderr(process, terminal, `fetch: unexpected argument: ${arg}`)
|
|
131
|
+
return 1
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
await writelnStderr(process, terminal, `fetch: invalid option -- '${arg.replace(/^-+/, '')}'`)
|
|
135
|
+
await writelnStderr(process, terminal, `Try 'fetch --help' for more information.`)
|
|
136
|
+
return 1
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!url) {
|
|
141
|
+
await writelnStderr(process, terminal, `fetch: URL is required`)
|
|
142
|
+
await writelnStderr(process, terminal, `Try 'fetch --help' for more information.`)
|
|
143
|
+
return 1
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const fetchOptions: RequestInit = { method }
|
|
148
|
+
if (body) fetchOptions.body = body
|
|
149
|
+
if (Object.keys(headers).length > 0) fetchOptions.headers = headers
|
|
150
|
+
|
|
151
|
+
const response = await globalThis.fetch(url, fetchOptions)
|
|
152
|
+
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
await writelnStderr(process, terminal, chalk.red(`fetch: HTTP error! status: ${response.status} ${response.statusText}`))
|
|
155
|
+
return 1
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const reader = response.body?.getReader()
|
|
159
|
+
if (!reader) {
|
|
160
|
+
await writelnStderr(process, terminal, chalk.red(`fetch: No response body`))
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let writer: WritableStreamDefaultWriter<Uint8Array> | { write: (chunk: Uint8Array) => Promise<void>, releaseLock: () => Promise<void> } | undefined
|
|
165
|
+
|
|
166
|
+
if (outputFile) {
|
|
167
|
+
// Write to file
|
|
168
|
+
const fullPath = path.resolve(shell.cwd, outputFile)
|
|
169
|
+
const fileHandle = await shell.context.fs.promises.open(fullPath, 'w')
|
|
170
|
+
writer = {
|
|
171
|
+
write: async (chunk: Uint8Array) => {
|
|
172
|
+
await fileHandle.write(chunk)
|
|
173
|
+
},
|
|
174
|
+
releaseLock: async () => {
|
|
175
|
+
await fileHandle.close()
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
// Write to stdout
|
|
180
|
+
if (!process.stdout) {
|
|
181
|
+
await writelnStderr(process, terminal, chalk.red(`fetch: No stdout available`))
|
|
182
|
+
return 1
|
|
183
|
+
}
|
|
184
|
+
writer = process.stdout.getWriter()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
while (true) {
|
|
189
|
+
const { done, value } = await reader.read()
|
|
190
|
+
if (done) break
|
|
191
|
+
if (value && value.length > 0) await writer.write(value)
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
reader.releaseLock()
|
|
195
|
+
if (writer && 'releaseLock' in writer) await writer.releaseLock()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return 0
|
|
199
|
+
} catch (error) {
|
|
200
|
+
await writelnStderr(process, terminal, chalk.red(`fetch: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
201
|
+
return 1
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
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: format [OPTION]...
|
|
8
|
+
Delete all IndexedDB and localStorage data.
|
|
9
|
+
|
|
10
|
+
-i, --indexeddb Delete only IndexedDB databases
|
|
11
|
+
-l, --localstorage Delete only localStorage
|
|
12
|
+
-k, --keep <name> Preserve specific IndexedDB database(s) (can be used multiple times)
|
|
13
|
+
--help Display this help and exit
|
|
14
|
+
|
|
15
|
+
By default, deletes all IndexedDB databases and localStorage.
|
|
16
|
+
Requires root privileges and interactive confirmation.`
|
|
17
|
+
writelnStderr(process, terminal, usage)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function emptyIndexedDBDatabases(
|
|
21
|
+
keepDatabases: string[],
|
|
22
|
+
_kernel: Kernel,
|
|
23
|
+
process: Process | undefined,
|
|
24
|
+
terminal: Terminal
|
|
25
|
+
): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
if (!globalThis.indexedDB) {
|
|
28
|
+
await writelnStderr(process, terminal, chalk.yellow('format: IndexedDB is not available'))
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await writelnStdout(process, terminal, chalk.yellow('format: Emptying filesystem...'))
|
|
33
|
+
|
|
34
|
+
let databases: Array<{ name: string; version: number }> = []
|
|
35
|
+
|
|
36
|
+
if (typeof indexedDB.databases === 'function') {
|
|
37
|
+
const dbList = await indexedDB.databases()
|
|
38
|
+
databases = dbList
|
|
39
|
+
.filter((db): db is { name: string; version: number } => typeof db.name === 'string')
|
|
40
|
+
.map(db => ({ name: db.name, version: db.version }))
|
|
41
|
+
} else {
|
|
42
|
+
await writelnStdout(process, terminal, chalk.yellow('format: indexedDB.databases() not supported, assuming filesystem will be emptied on reboot'))
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const databasesToEmpty = databases.filter(db => !keepDatabases.includes(db.name))
|
|
47
|
+
|
|
48
|
+
if (databasesToEmpty.length === 0) {
|
|
49
|
+
await writelnStdout(process, terminal, chalk.green('format: All databases are preserved, nothing to empty'))
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (keepDatabases.length > 0) {
|
|
54
|
+
const preserved = databases.filter(db => keepDatabases.includes(db.name))
|
|
55
|
+
if (preserved.length > 0) {
|
|
56
|
+
await writelnStdout(process, terminal, chalk.cyan(`format: Preserving databases: ${preserved.map(db => db.name).join(', ')}`))
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await writelnStdout(process, terminal, chalk.green(`format: Will empty ${databasesToEmpty.length} IndexedDB database(s) on reboot`))
|
|
61
|
+
return true
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
64
|
+
await writelnStderr(process, terminal, chalk.red(`format: Error preparing IndexedDB databases: ${errorMessage}`))
|
|
65
|
+
return false
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function deleteLocalStorage(
|
|
70
|
+
process: Process | undefined,
|
|
71
|
+
terminal: Terminal,
|
|
72
|
+
storage: Storage
|
|
73
|
+
): Promise<boolean> {
|
|
74
|
+
try {
|
|
75
|
+
const keysCount = storage.length
|
|
76
|
+
if (keysCount === 0) {
|
|
77
|
+
await writelnStdout(process, terminal, chalk.green('format: localStorage is already empty'))
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
storage.clear()
|
|
82
|
+
await writelnStdout(process, terminal, chalk.green(`format: Successfully cleared localStorage (${keysCount} item(s))`))
|
|
83
|
+
return true
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
86
|
+
await writelnStderr(process, terminal, chalk.red(`format: Error clearing localStorage: ${errorMessage}`))
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
92
|
+
return new TerminalCommand({
|
|
93
|
+
command: 'format',
|
|
94
|
+
description: 'Delete all IndexedDB and localStorage data',
|
|
95
|
+
kernel,
|
|
96
|
+
shell,
|
|
97
|
+
terminal,
|
|
98
|
+
run: async (pid: number, argv: string[]) => {
|
|
99
|
+
const process = kernel.processes.get(pid) as Process | undefined
|
|
100
|
+
|
|
101
|
+
if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
|
|
102
|
+
printUsage(process, terminal)
|
|
103
|
+
return 0
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (shell.credentials.suid !== 0) {
|
|
107
|
+
await writelnStderr(process, terminal, chalk.red('format: permission denied (requires root)'))
|
|
108
|
+
return 1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let onlyIndexedDB = false
|
|
112
|
+
let onlyLocalStorage = false
|
|
113
|
+
const keepDatabases: string[] = []
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < argv.length; i++) {
|
|
116
|
+
const arg = argv[i]
|
|
117
|
+
if (!arg || typeof arg !== 'string') continue
|
|
118
|
+
|
|
119
|
+
if (arg === '-i' || arg === '--indexeddb') {
|
|
120
|
+
onlyIndexedDB = true
|
|
121
|
+
} else if (arg === '-l' || arg === '--localstorage') {
|
|
122
|
+
onlyLocalStorage = true
|
|
123
|
+
} else if (arg === '-k' || arg === '--keep') {
|
|
124
|
+
if (i + 1 < argv.length) {
|
|
125
|
+
const dbName = argv[++i]
|
|
126
|
+
if (dbName && !dbName.startsWith('-')) {
|
|
127
|
+
keepDatabases.push(dbName)
|
|
128
|
+
} else {
|
|
129
|
+
await writelnStderr(process, terminal, chalk.red('format: --keep requires a database name'))
|
|
130
|
+
return 1
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
await writelnStderr(process, terminal, chalk.red('format: --keep requires a database name'))
|
|
134
|
+
return 1
|
|
135
|
+
}
|
|
136
|
+
} else if (arg.startsWith('-')) {
|
|
137
|
+
await writelnStderr(process, terminal, chalk.red(`format: invalid option -- '${arg.replace(/^-+/, '')}'`))
|
|
138
|
+
await writelnStdout(process, terminal, "Try 'format --help' for more information.")
|
|
139
|
+
return 1
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (onlyIndexedDB && onlyLocalStorage) {
|
|
144
|
+
await writelnStderr(process, terminal, chalk.red('format: cannot specify both --indexeddb and --localstorage'))
|
|
145
|
+
return 1
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const deleteIndexedDB = !onlyLocalStorage
|
|
149
|
+
const deleteLocal = !onlyIndexedDB
|
|
150
|
+
|
|
151
|
+
let actionDescription = 'This will delete '
|
|
152
|
+
if (deleteIndexedDB && deleteLocal) {
|
|
153
|
+
actionDescription += 'ALL IndexedDB databases and localStorage data'
|
|
154
|
+
} else if (deleteIndexedDB) {
|
|
155
|
+
actionDescription += 'ALL IndexedDB databases'
|
|
156
|
+
} else {
|
|
157
|
+
actionDescription += 'ALL localStorage data'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (keepDatabases.length > 0) {
|
|
161
|
+
actionDescription += ` (preserving: ${keepDatabases.join(', ')})`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
actionDescription += '. This action cannot be undone!'
|
|
165
|
+
|
|
166
|
+
await writelnStderr(process, terminal, chalk.red.bold(`⚠️ WARNING: ${actionDescription}`))
|
|
167
|
+
await writelnStdout(process, terminal, chalk.yellow('Type "yes" to continue, or anything else to cancel: '))
|
|
168
|
+
|
|
169
|
+
const confirmation = await terminal.readline()
|
|
170
|
+
if (confirmation.trim().toLowerCase() !== 'yes') {
|
|
171
|
+
await writelnStdout(process, terminal, chalk.yellow('format: Operation cancelled'))
|
|
172
|
+
return 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (kernel.storage.db) {
|
|
176
|
+
kernel.storage.db.close()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let success = true
|
|
180
|
+
|
|
181
|
+
if (deleteIndexedDB) {
|
|
182
|
+
const indexedDBSuccess = await emptyIndexedDBDatabases(keepDatabases, kernel, process, terminal)
|
|
183
|
+
success = success && indexedDBSuccess
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (deleteLocal) {
|
|
187
|
+
const localStorageSuccess = await deleteLocalStorage(process, terminal, kernel.storage.local)
|
|
188
|
+
success = success && localStorageSuccess
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (success) {
|
|
192
|
+
await writelnStdout(process, terminal, chalk.green.bold('format: Format operation completed successfully'))
|
|
193
|
+
await writelnStdout(process, terminal, chalk.yellow('format: Rebooting system to complete format...'))
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
kernel.reboot()
|
|
196
|
+
}, 500)
|
|
197
|
+
return 0
|
|
198
|
+
} else {
|
|
199
|
+
await writelnStderr(process, terminal, chalk.red.bold('format: Format operation completed with errors'))
|
|
200
|
+
return 1
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
}
|