command-stream 0.3.0 → 0.3.2
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/README.md +4 -1
- package/package.json +4 -4
- package/{$.mjs → src/$.mjs} +95 -675
- package/src/$.utils.mjs +81 -0
- package/src/commands/$.basename.mjs +19 -0
- package/src/commands/$.cat.mjs +38 -0
- package/src/commands/$.cd.mjs +16 -0
- package/src/commands/$.cp.mjs +112 -0
- package/src/commands/$.dirname.mjs +12 -0
- package/src/commands/$.echo.mjs +15 -0
- package/src/commands/$.env.mjs +14 -0
- package/src/commands/$.exit.mjs +9 -0
- package/src/commands/$.false.mjs +3 -0
- package/src/commands/$.ls.mjs +79 -0
- package/src/commands/$.mkdir.mjs +45 -0
- package/src/commands/$.mv.mjs +89 -0
- package/src/commands/$.pwd.mjs +8 -0
- package/src/commands/$.rm.mjs +60 -0
- package/src/commands/$.seq.mjs +48 -0
- package/src/commands/$.sleep.mjs +15 -0
- package/src/commands/$.test.mjs +59 -0
- package/src/commands/$.touch.mjs +36 -0
- package/src/commands/$.true.mjs +5 -0
- package/src/commands/$.which.mjs +32 -0
- package/src/commands/$.yes.mjs +34 -0
package/src/$.utils.mjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
const VERBOSE = process.env.COMMAND_STREAM_VERBOSE === 'true';
|
|
4
|
+
|
|
5
|
+
export function trace(category, messageOrFunc) {
|
|
6
|
+
if (!VERBOSE) return;
|
|
7
|
+
const message = typeof messageOrFunc === 'function' ? messageOrFunc() : messageOrFunc;
|
|
8
|
+
const timestamp = new Date().toISOString();
|
|
9
|
+
console.error(`[TRACE ${timestamp}] [${category}] ${message}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const VirtualUtils = {
|
|
13
|
+
/**
|
|
14
|
+
* Create standardized error response for missing operands
|
|
15
|
+
*/
|
|
16
|
+
missingOperandError(commandName, customMessage = null) {
|
|
17
|
+
const message = customMessage || `${commandName}: missing operand`;
|
|
18
|
+
return { stderr: message, code: 1 };
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create standardized error response for invalid arguments
|
|
23
|
+
*/
|
|
24
|
+
invalidArgumentError(commandName, message) {
|
|
25
|
+
return { stderr: `${commandName}: ${message}`, code: 1 };
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create standardized success response
|
|
30
|
+
*/
|
|
31
|
+
success(stdout = '', code = 0) {
|
|
32
|
+
return { stdout, stderr: '', code };
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create standardized error response
|
|
37
|
+
*/
|
|
38
|
+
error(stderr = '', code = 1) {
|
|
39
|
+
return { stdout: '', stderr, code };
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate that command has required number of arguments
|
|
44
|
+
*/
|
|
45
|
+
validateArgs(args, minCount, commandName) {
|
|
46
|
+
if (args.length < minCount) {
|
|
47
|
+
if (minCount === 1) {
|
|
48
|
+
return this.missingOperandError(commandName);
|
|
49
|
+
} else {
|
|
50
|
+
return this.invalidArgumentError(commandName, `requires at least ${minCount} arguments`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return null; // No error
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve file path with optional cwd parameter
|
|
58
|
+
*/
|
|
59
|
+
resolvePath(filePath, cwd = null) {
|
|
60
|
+
const basePath = cwd || process.cwd();
|
|
61
|
+
return path.isAbsolute(filePath) ? filePath : path.resolve(basePath, filePath);
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Safe file system operation wrapper
|
|
66
|
+
*/
|
|
67
|
+
async safeFsOperation(operation, errorPrefix) {
|
|
68
|
+
try {
|
|
69
|
+
return await operation();
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return this.error(`${errorPrefix}: ${error.message}`);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create async wrapper for Promise-based operations
|
|
77
|
+
*/
|
|
78
|
+
createAsyncWrapper(promiseFactory) {
|
|
79
|
+
return new Promise(promiseFactory);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { VirtualUtils } from '../$.utils.mjs';
|
|
3
|
+
|
|
4
|
+
export default async function basename({ args }) {
|
|
5
|
+
const argError = VirtualUtils.validateArgs(args, 1, 'basename');
|
|
6
|
+
if (argError) return argError;
|
|
7
|
+
|
|
8
|
+
const filePath = args[0];
|
|
9
|
+
const suffix = args[1];
|
|
10
|
+
|
|
11
|
+
let result = path.basename(filePath);
|
|
12
|
+
|
|
13
|
+
// Remove suffix if provided and it matches
|
|
14
|
+
if (suffix && result.endsWith(suffix)) {
|
|
15
|
+
result = result.slice(0, -suffix.length);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return VirtualUtils.success(result + '\n');
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
3
|
+
|
|
4
|
+
export default async function cat({ args, stdin, cwd }) {
|
|
5
|
+
if (args.length === 0) {
|
|
6
|
+
// Read from stdin if no files specified
|
|
7
|
+
if (stdin !== undefined && stdin !== '') {
|
|
8
|
+
return VirtualUtils.success(stdin);
|
|
9
|
+
}
|
|
10
|
+
return VirtualUtils.success();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const outputs = [];
|
|
15
|
+
for (const file of args) {
|
|
16
|
+
trace('VirtualCommand', () => `cat: reading file | ${JSON.stringify({ file }, null, 2)}`);
|
|
17
|
+
const resolvedPath = VirtualUtils.resolvePath(file, cwd);
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
20
|
+
outputs.push(content);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error.code === 'ENOENT') {
|
|
23
|
+
return VirtualUtils.error(`cat: ${file}: No such file or directory`);
|
|
24
|
+
} else if (error.code === 'EISDIR') {
|
|
25
|
+
return VirtualUtils.error(`cat: ${file}: Is a directory`);
|
|
26
|
+
} else {
|
|
27
|
+
return VirtualUtils.error(`cat: ${file}: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const output = outputs.join('');
|
|
32
|
+
trace('VirtualCommand', () => `cat: success | ${JSON.stringify({ bytesRead: output.length }, null, 2)}`);
|
|
33
|
+
return VirtualUtils.success(output);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
trace('VirtualCommand', () => `cat: unexpected error | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
36
|
+
return VirtualUtils.error(`cat: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function cd({ args }) {
|
|
4
|
+
const target = args[0] || process.env.HOME || process.env.USERPROFILE || '/';
|
|
5
|
+
trace('VirtualCommand', () => `cd: changing directory | ${JSON.stringify({ target }, null, 2)}`);
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
process.chdir(target);
|
|
9
|
+
const newDir = process.cwd();
|
|
10
|
+
trace('VirtualCommand', () => `cd: success | ${JSON.stringify({ newDir }, null, 2)}`);
|
|
11
|
+
return VirtualUtils.success(newDir);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
trace('VirtualCommand', () => `cd: failed | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
14
|
+
return { stderr: `cd: ${error.message}`, code: 1 };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
4
|
+
|
|
5
|
+
export default async function cp({ args, stdin, cwd }) {
|
|
6
|
+
const argError = VirtualUtils.validateArgs(args, 2, 'cp');
|
|
7
|
+
if (argError) return VirtualUtils.invalidArgumentError('cp', 'missing destination file operand');
|
|
8
|
+
|
|
9
|
+
// Parse flags and paths
|
|
10
|
+
const flags = new Set();
|
|
11
|
+
const paths = [];
|
|
12
|
+
|
|
13
|
+
for (const arg of args) {
|
|
14
|
+
if (arg === '-r' || arg === '-R' || arg === '--recursive') {
|
|
15
|
+
flags.add('r');
|
|
16
|
+
} else if (arg.startsWith('-')) {
|
|
17
|
+
for (const flag of arg.slice(1)) {
|
|
18
|
+
flags.add(flag);
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
paths.push(arg);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (paths.length < 2) {
|
|
26
|
+
return VirtualUtils.invalidArgumentError('cp', 'missing destination file operand');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const recursive = flags.has('r') || flags.has('R');
|
|
30
|
+
const sources = paths.slice(0, -1);
|
|
31
|
+
const destination = paths[paths.length - 1];
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const destPath = VirtualUtils.resolvePath(destination, cwd);
|
|
35
|
+
let destExists = false;
|
|
36
|
+
let destIsDir = false;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const destStats = fs.statSync(destPath);
|
|
40
|
+
destExists = true;
|
|
41
|
+
destIsDir = destStats.isDirectory();
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error.code !== 'ENOENT') throw error;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Copying multiple files requires destination to be a directory
|
|
47
|
+
if (sources.length > 1 && destExists && !destIsDir) {
|
|
48
|
+
return VirtualUtils.error(`cp: target '${destination}' is not a directory`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Helper function to copy directory recursively
|
|
52
|
+
const copyRecursive = (src, dest) => {
|
|
53
|
+
const stats = fs.statSync(src);
|
|
54
|
+
|
|
55
|
+
if (stats.isDirectory()) {
|
|
56
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
57
|
+
const entries = fs.readdirSync(src);
|
|
58
|
+
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
copyRecursive(
|
|
61
|
+
path.join(src, entry),
|
|
62
|
+
path.join(dest, entry)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
fs.copyFileSync(src, dest);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
for (const source of sources) {
|
|
71
|
+
const sourcePath = VirtualUtils.resolvePath(source, cwd);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const sourceStats = fs.statSync(sourcePath);
|
|
75
|
+
let finalDestPath = destPath;
|
|
76
|
+
|
|
77
|
+
if (destIsDir || (sources.length > 1 && !destExists)) {
|
|
78
|
+
// Copying into a directory
|
|
79
|
+
if (!destExists) {
|
|
80
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
81
|
+
destIsDir = true;
|
|
82
|
+
destExists = true;
|
|
83
|
+
}
|
|
84
|
+
finalDestPath = path.join(destPath, path.basename(sourcePath));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (sourceStats.isDirectory()) {
|
|
88
|
+
if (!recursive) {
|
|
89
|
+
return VirtualUtils.error(`cp: -r not specified; omitting directory '${source}'`);
|
|
90
|
+
}
|
|
91
|
+
trace('VirtualCommand', () => `cp: copying directory | ${JSON.stringify({ from: sourcePath, to: finalDestPath }, null, 2)}`);
|
|
92
|
+
copyRecursive(sourcePath, finalDestPath);
|
|
93
|
+
} else {
|
|
94
|
+
trace('VirtualCommand', () => `cp: copying file | ${JSON.stringify({ from: sourcePath, to: finalDestPath }, null, 2)}`);
|
|
95
|
+
fs.copyFileSync(sourcePath, finalDestPath);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT') {
|
|
100
|
+
return VirtualUtils.error(`cp: cannot stat '${source}': No such file or directory`);
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
trace('VirtualCommand', () => `cp: success | ${JSON.stringify({ filesCopied: sources.length }, null, 2)}`);
|
|
107
|
+
return VirtualUtils.success();
|
|
108
|
+
} catch (error) {
|
|
109
|
+
trace('VirtualCommand', () => `cp: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
110
|
+
return VirtualUtils.error(`cp: ${error.message}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { VirtualUtils } from '../$.utils.mjs';
|
|
3
|
+
|
|
4
|
+
export default async function dirname({ args }) {
|
|
5
|
+
const argError = VirtualUtils.validateArgs(args, 1, 'dirname');
|
|
6
|
+
if (argError) return argError;
|
|
7
|
+
|
|
8
|
+
const filePath = args[0];
|
|
9
|
+
const result = path.dirname(filePath);
|
|
10
|
+
|
|
11
|
+
return VirtualUtils.success(result + '\n');
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function echo({ args }) {
|
|
4
|
+
trace('VirtualCommand', () => `echo: processing | ${JSON.stringify({ argsCount: args.length }, null, 2)}`);
|
|
5
|
+
|
|
6
|
+
let output = args.join(' ');
|
|
7
|
+
if (args.includes('-n')) {
|
|
8
|
+
// Don't add newline
|
|
9
|
+
trace('VirtualCommand', () => `BRANCH: echo => NO_NEWLINE | ${JSON.stringify({}, null, 2)}`);
|
|
10
|
+
output = args.filter(arg => arg !== '-n').join(' ');
|
|
11
|
+
} else {
|
|
12
|
+
output += '\n';
|
|
13
|
+
}
|
|
14
|
+
return VirtualUtils.success(output);
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { VirtualUtils } from '../$.utils.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function env({ args, stdin, env }) {
|
|
4
|
+
if (args.length === 0) {
|
|
5
|
+
// Use custom env if provided, otherwise use process.env
|
|
6
|
+
const envVars = env || process.env;
|
|
7
|
+
const output = Object.entries(envVars)
|
|
8
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
9
|
+
.join('\n') + '\n';
|
|
10
|
+
return VirtualUtils.success(output);
|
|
11
|
+
}
|
|
12
|
+
// TODO: Support setting environment variables for subsequent command
|
|
13
|
+
return VirtualUtils.error('env: setting variables not yet supported');
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function createExitCommand(globalShellSettings) {
|
|
2
|
+
return async function exit({ args }) {
|
|
3
|
+
const code = parseInt(args[0] || 0);
|
|
4
|
+
if (globalShellSettings.errexit || code !== 0) {
|
|
5
|
+
throw { code, message: `Command failed with exit code ${code}` };
|
|
6
|
+
}
|
|
7
|
+
return { stdout: '', code };
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
4
|
+
|
|
5
|
+
export default async function ls({ args, stdin, cwd }) {
|
|
6
|
+
try {
|
|
7
|
+
// Parse flags
|
|
8
|
+
const flags = new Set();
|
|
9
|
+
const paths = [];
|
|
10
|
+
|
|
11
|
+
for (const arg of args) {
|
|
12
|
+
if (arg.startsWith('-')) {
|
|
13
|
+
for (const flag of arg.slice(1)) {
|
|
14
|
+
flags.add(flag);
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
paths.push(arg);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Default to current directory if no paths specified
|
|
22
|
+
if (paths.length === 0) {
|
|
23
|
+
paths.push(cwd || process.cwd());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const showAll = flags.has('a');
|
|
27
|
+
const longFormat = flags.has('l');
|
|
28
|
+
|
|
29
|
+
trace('VirtualCommand', () => `ls: listing | ${JSON.stringify({ paths, flags: Array.from(flags) }, null, 2)}`);
|
|
30
|
+
|
|
31
|
+
const outputs = [];
|
|
32
|
+
|
|
33
|
+
for (const targetPath of paths) {
|
|
34
|
+
const resolvedPath = VirtualUtils.resolvePath(targetPath, cwd);
|
|
35
|
+
const stats = fs.statSync(resolvedPath);
|
|
36
|
+
|
|
37
|
+
if (stats.isDirectory()) {
|
|
38
|
+
let entries = fs.readdirSync(resolvedPath);
|
|
39
|
+
|
|
40
|
+
if (!showAll) {
|
|
41
|
+
entries = entries.filter(e => !e.startsWith('.'));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (longFormat) {
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const entryPath = path.join(resolvedPath, entry);
|
|
47
|
+
const entryStats = fs.statSync(entryPath);
|
|
48
|
+
const mode = entryStats.isDirectory() ? 'drwxr-xr-x' : '-rw-r--r--';
|
|
49
|
+
const size = entryStats.size.toString().padStart(8);
|
|
50
|
+
const mtime = entryStats.mtime.toISOString().split('T')[0];
|
|
51
|
+
outputs.push(`${mode} 1 user group ${size} ${mtime} ${entry}\n`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
outputs.push(entries.join('\n') + '\n');
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
// Single file
|
|
58
|
+
if (longFormat) {
|
|
59
|
+
const mode = '-rw-r--r--';
|
|
60
|
+
const size = stats.size.toString().padStart(8);
|
|
61
|
+
const mtime = stats.mtime.toISOString().split('T')[0];
|
|
62
|
+
const basename = path.basename(resolvedPath);
|
|
63
|
+
outputs.push(`${mode} 1 user group ${size} ${mtime} ${basename}\n`);
|
|
64
|
+
} else {
|
|
65
|
+
outputs.push(path.basename(resolvedPath) + '\n');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
trace('VirtualCommand', () => `ls: success | ${JSON.stringify({ entriesCount: outputs.length }, null, 2)}`);
|
|
71
|
+
return VirtualUtils.success(outputs.join(''));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
trace('VirtualCommand', () => `ls: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
74
|
+
if (error.code === 'ENOENT') {
|
|
75
|
+
return VirtualUtils.error(`ls: ${args[0] || '.'}: No such file or directory`);
|
|
76
|
+
}
|
|
77
|
+
return VirtualUtils.error(`ls: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
3
|
+
|
|
4
|
+
export default async function mkdir({ args, stdin, cwd }) {
|
|
5
|
+
const argError = VirtualUtils.validateArgs(args, 1, 'mkdir');
|
|
6
|
+
if (argError) return argError;
|
|
7
|
+
|
|
8
|
+
// Parse flags and paths
|
|
9
|
+
const flags = new Set();
|
|
10
|
+
const paths = [];
|
|
11
|
+
|
|
12
|
+
for (const arg of args) {
|
|
13
|
+
if (arg === '-p' || arg === '--parents') {
|
|
14
|
+
flags.add('p');
|
|
15
|
+
} else if (arg.startsWith('-')) {
|
|
16
|
+
for (const flag of arg.slice(1)) {
|
|
17
|
+
flags.add(flag);
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
paths.push(arg);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (paths.length === 0) {
|
|
25
|
+
return VirtualUtils.missingOperandError('mkdir');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const recursive = flags.has('p');
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
for (const dir of paths) {
|
|
32
|
+
const resolvedPath = VirtualUtils.resolvePath(dir, cwd);
|
|
33
|
+
trace('VirtualCommand', () => `mkdir: creating | ${JSON.stringify({ dir: resolvedPath, recursive }, null, 2)}`);
|
|
34
|
+
fs.mkdirSync(resolvedPath, { recursive });
|
|
35
|
+
}
|
|
36
|
+
trace('VirtualCommand', () => `mkdir: success | ${JSON.stringify({ dirsCreated: paths.length }, null, 2)}`);
|
|
37
|
+
return VirtualUtils.success();
|
|
38
|
+
} catch (error) {
|
|
39
|
+
trace('VirtualCommand', () => `mkdir: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
40
|
+
if (error.code === 'EEXIST') {
|
|
41
|
+
return VirtualUtils.error(`mkdir: cannot create directory '${paths[0]}': File exists`);
|
|
42
|
+
}
|
|
43
|
+
return VirtualUtils.error(`mkdir: ${error.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
4
|
+
|
|
5
|
+
export default async function mv({ args, stdin, cwd }) {
|
|
6
|
+
const argError = VirtualUtils.validateArgs(args, 2, 'mv');
|
|
7
|
+
if (argError) return VirtualUtils.invalidArgumentError('mv', 'missing destination file operand');
|
|
8
|
+
|
|
9
|
+
// Parse flags and paths
|
|
10
|
+
const flags = new Set();
|
|
11
|
+
const paths = [];
|
|
12
|
+
|
|
13
|
+
for (const arg of args) {
|
|
14
|
+
if (arg.startsWith('-')) {
|
|
15
|
+
for (const flag of arg.slice(1)) {
|
|
16
|
+
flags.add(flag);
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
paths.push(arg);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (paths.length < 2) {
|
|
24
|
+
return VirtualUtils.invalidArgumentError('mv', 'missing destination file operand');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const force = flags.has('f');
|
|
28
|
+
const sources = paths.slice(0, -1);
|
|
29
|
+
const destination = paths[paths.length - 1];
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const destPath = VirtualUtils.resolvePath(destination, cwd);
|
|
33
|
+
let destExists = false;
|
|
34
|
+
let destIsDir = false;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const destStats = fs.statSync(destPath);
|
|
38
|
+
destExists = true;
|
|
39
|
+
destIsDir = destStats.isDirectory();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code !== 'ENOENT') throw error;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Moving multiple files requires destination to be a directory
|
|
45
|
+
if (sources.length > 1 && destExists && !destIsDir) {
|
|
46
|
+
return VirtualUtils.error(`mv: target '${destination}' is not a directory`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const source of sources) {
|
|
50
|
+
const sourcePath = VirtualUtils.resolvePath(source, cwd);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const sourceStats = fs.statSync(sourcePath);
|
|
54
|
+
let finalDestPath = destPath;
|
|
55
|
+
|
|
56
|
+
if (destIsDir) {
|
|
57
|
+
// Moving into a directory
|
|
58
|
+
finalDestPath = path.join(destPath, path.basename(sourcePath));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if destination exists and handle force flag
|
|
62
|
+
if (!force) {
|
|
63
|
+
try {
|
|
64
|
+
fs.statSync(finalDestPath);
|
|
65
|
+
// Destination exists and no force flag
|
|
66
|
+
return VirtualUtils.error(`mv: cannot move '${source}': File exists`);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.code !== 'ENOENT') throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
trace('VirtualCommand', () => `mv: moving | ${JSON.stringify({ from: sourcePath, to: finalDestPath }, null, 2)}`);
|
|
73
|
+
fs.renameSync(sourcePath, finalDestPath);
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error.code === 'ENOENT') {
|
|
77
|
+
return VirtualUtils.error(`mv: cannot stat '${source}': No such file or directory`);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
trace('VirtualCommand', () => `mv: success | ${JSON.stringify({ filesMoved: sources.length }, null, 2)}`);
|
|
84
|
+
return VirtualUtils.success();
|
|
85
|
+
} catch (error) {
|
|
86
|
+
trace('VirtualCommand', () => `mv: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
87
|
+
return VirtualUtils.error(`mv: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function pwd({ args, stdin, cwd }) {
|
|
4
|
+
// If cwd option is provided, return that instead of process.cwd()
|
|
5
|
+
const dir = cwd || process.cwd();
|
|
6
|
+
trace('VirtualCommand', () => `pwd: getting directory | ${JSON.stringify({ dir }, null, 2)}`);
|
|
7
|
+
return VirtualUtils.success(dir);
|
|
8
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { trace, VirtualUtils } from '../$.utils.mjs';
|
|
3
|
+
|
|
4
|
+
export default async function rm({ args, stdin, cwd }) {
|
|
5
|
+
const argError = VirtualUtils.validateArgs(args, 1, 'rm');
|
|
6
|
+
if (argError) return argError;
|
|
7
|
+
|
|
8
|
+
// Parse flags and paths
|
|
9
|
+
const flags = new Set();
|
|
10
|
+
const paths = [];
|
|
11
|
+
|
|
12
|
+
for (const arg of args) {
|
|
13
|
+
if (arg.startsWith('-')) {
|
|
14
|
+
for (const flag of arg.slice(1)) {
|
|
15
|
+
flags.add(flag);
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
paths.push(arg);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (paths.length === 0) {
|
|
23
|
+
return VirtualUtils.missingOperandError('rm');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const recursive = flags.has('r') || flags.has('R');
|
|
27
|
+
const force = flags.has('f');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
for (const file of paths) {
|
|
31
|
+
const resolvedPath = VirtualUtils.resolvePath(file, cwd);
|
|
32
|
+
trace('VirtualCommand', () => `rm: removing | ${JSON.stringify({ file: resolvedPath, recursive, force }, null, 2)}`);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const stats = fs.statSync(resolvedPath);
|
|
36
|
+
if (stats.isDirectory() && !recursive) {
|
|
37
|
+
return VirtualUtils.error(`rm: cannot remove '${file}': Is a directory`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (stats.isDirectory()) {
|
|
41
|
+
fs.rmSync(resolvedPath, { recursive: true, force });
|
|
42
|
+
} else {
|
|
43
|
+
fs.unlinkSync(resolvedPath);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === 'ENOENT' && !force) {
|
|
47
|
+
return VirtualUtils.error(`rm: cannot remove '${file}': No such file or directory`);
|
|
48
|
+
} else if (error.code !== 'ENOENT') {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
trace('VirtualCommand', () => `rm: success | ${JSON.stringify({ filesRemoved: paths.length }, null, 2)}`);
|
|
55
|
+
return VirtualUtils.success();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
trace('VirtualCommand', () => `rm: error | ${JSON.stringify({ error: error.message }, null, 2)}`);
|
|
58
|
+
return VirtualUtils.error(`rm: ${error.message}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { VirtualUtils } from '../$.utils.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function seq({ args }) {
|
|
4
|
+
const argError = VirtualUtils.validateArgs(args, 1, 'seq');
|
|
5
|
+
if (argError) return argError;
|
|
6
|
+
|
|
7
|
+
let start, step, end;
|
|
8
|
+
|
|
9
|
+
if (args.length === 1) {
|
|
10
|
+
// seq END
|
|
11
|
+
start = 1;
|
|
12
|
+
step = 1;
|
|
13
|
+
end = parseFloat(args[0]);
|
|
14
|
+
} else if (args.length === 2) {
|
|
15
|
+
// seq START END
|
|
16
|
+
start = parseFloat(args[0]);
|
|
17
|
+
step = 1;
|
|
18
|
+
end = parseFloat(args[1]);
|
|
19
|
+
} else {
|
|
20
|
+
// seq START STEP END
|
|
21
|
+
start = parseFloat(args[0]);
|
|
22
|
+
step = parseFloat(args[1]);
|
|
23
|
+
end = parseFloat(args[2]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Validate numbers
|
|
27
|
+
if (isNaN(start) || isNaN(step) || isNaN(end)) {
|
|
28
|
+
return VirtualUtils.error('seq: invalid number');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (step === 0) {
|
|
32
|
+
return VirtualUtils.error('seq: step cannot be zero');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const output = [];
|
|
36
|
+
|
|
37
|
+
if (step > 0) {
|
|
38
|
+
for (let i = start; i <= end; i += step) {
|
|
39
|
+
output.push(i.toString());
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
for (let i = start; i >= end; i += step) {
|
|
43
|
+
output.push(i.toString());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return VirtualUtils.success(output.join('\n') + (output.length > 0 ? '\n' : ''));
|
|
48
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { trace } from '../$.utils.mjs';
|
|
2
|
+
|
|
3
|
+
export default async function sleep({ args }) {
|
|
4
|
+
const seconds = parseFloat(args[0] || 0);
|
|
5
|
+
trace('VirtualCommand', () => `sleep: starting | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
6
|
+
|
|
7
|
+
if (isNaN(seconds) || seconds < 0) {
|
|
8
|
+
return { stderr: `sleep: invalid time interval '${args[0]}'`, code: 1 };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
await new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
|
12
|
+
trace('VirtualCommand', () => `sleep: completed | ${JSON.stringify({ seconds }, null, 2)}`);
|
|
13
|
+
|
|
14
|
+
return { stdout: '', code: 0 };
|
|
15
|
+
}
|