Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 40 additions & 5 deletions src/cli/interactive.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
import readline from 'readline';

const interactive = () => {
// Write your code here
// Use readline module for interactive CLI
// Support commands: uptime, cwd, date, exit
// Handle Ctrl+C and unknown commands
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: '> '
});

const exitHandler = () => {
console.log('Goodbye!');
rl.close();
process.exit(0);
};

rl.on('SIGINT', exitHandler);
rl.on('close', exitHandler);

rl.prompt();

rl.on('line', (line) => {
const cmd = line.trim();
switch (cmd) {
case 'uptime':
console.log(`Uptime: ${process.uptime().toFixed(2)}s`);
break;
case 'cwd':
console.log(process.cwd());
break;
case 'date':
console.log(new Date().toISOString());
break;
case 'exit':
exitHandler();
return;
default:
console.log('Unknown command');
}
rl.prompt();
});
};

interactive();
interactive();
48 changes: 43 additions & 5 deletions src/cli/progress.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
const parseArg = (name, def) => {
const idx = process.argv.indexOf('--' + name);
if (idx !== -1 && process.argv[idx + 1]) {
return process.argv[idx + 1];
}
return def;
};

const isValidHex = (hex) => /^#[0-9a-fA-F]{6}$/.test(hex);

const progress = () => {
// Write your code here
// Simulate progress bar from 0% to 100% over ~5 seconds
// Update in place using \r every 100ms
// Format: [████████████████████ ] 67%
const duration = Number(parseArg('duration', 5000));
const interval = Number(parseArg('interval', 100));
const length = Number(parseArg('length', 30));
const color = parseArg('color', null);
const useColor = color && isValidHex(color);
let percent = 0;
let elapsed = 0;
const totalSteps = Math.ceil(duration / interval);

const colorStart = useColor
? `\x1b[38;2;${parseInt(color.slice(1, 3), 16)};${parseInt(color.slice(3, 5), 16)};${parseInt(color.slice(5, 7), 16)}m`
: '';
const colorEnd = useColor ? '\x1b[0m' : '';

const timer = setInterval(() => {
percent = Math.min(1, elapsed / duration);
const filled = Math.round(length * percent);
const empty = length - filled;
const bar =
'[' +
(useColor ? colorStart : '') +
'█'.repeat(filled) +
(useColor ? colorEnd : '') +
' '.repeat(empty) +
`] ${Math.round(percent * 100)}%`;
process.stdout.write('\r' + bar);
elapsed += interval;
if (percent >= 1) {
clearInterval(timer);
process.stdout.write('\nDone!\n');
}
}, interval);
};

progress();
progress();
26 changes: 19 additions & 7 deletions src/cp/execCommand.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { spawn } from 'child_process';

const execCommand = () => {
// Write your code here
// Take command from CLI argument
// Spawn child process
// Pipe child stdout/stderr to parent stdout/stderr
// Pass environment variables
// Exit with same code as child
const cmdStr = process.argv[2];
if (!cmdStr) {
console.error('No command provided');
process.exit(1);
}
// Split command into executable and args
const [command, ...args] = cmdStr.match(/(?:[^\s"]+|"[^"]*")+/g).map(s => s.replace(/^"|"$/g, ''));
const child = spawn(command, args, {
stdio: 'inherit',
env: process.env,
shell: process.platform === 'win32' // for Windows compatibility
});

child.on('exit', (code) => {
process.exit(code);
});
};

execCommand();
execCommand();
50 changes: 47 additions & 3 deletions src/fs/findByExt.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,51 @@
import fs from 'fs/promises';
import path from 'path';

const WORKSPACE = process.cwd();

function parseExtArg() {
const extIndex = process.argv.indexOf('--ext');
let ext = '.txt';
if (extIndex !== -1 && process.argv[extIndex + 1]) {
ext = process.argv[extIndex + 1];
if (!ext.startsWith('.')) ext = '.' + ext;
}
return ext;
}

async function* walk(dir) {
let entries;
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch (e) {
throw new Error('FS operation failed');
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
yield* walk(fullPath);
} else if (entry.isFile()) {
yield fullPath;
}
}
}

const findByExt = async () => {
// Write your code here
// Recursively find all files with specific extension
// Parse --ext CLI argument (default: .txt)
const ext = parseExtArg();
let files = [];
try {
for await (const file of walk(WORKSPACE)) {
if (path.extname(file) === ext) {
files.push(path.relative(WORKSPACE, file));
}
}
} catch (e) {
throw new Error('FS operation failed');
}
files.sort();
for (const f of files) {
console.log(f);
}
};

await findByExt();
55 changes: 50 additions & 5 deletions src/fs/merge.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,53 @@
import fs from 'fs/promises';
import path from 'path';

const PARTS_DIR = path.join(process.cwd(), 'workspace', 'parts');
const MERGED_FILE = path.join(process.cwd(), 'workspace', 'merged.txt');

const parseFilesArg = () => {
const idx = process.argv.indexOf('--files');
if (idx !== -1 && process.argv[idx + 1]) {
return process.argv[idx + 1].split(',').map(f => f.trim()).filter(Boolean);
}
return null;
};

const merge = async () => {
// Write your code here
// Default: read all .txt files from workspace/parts in alphabetical order
// Optional: support --files filename1,filename2,... to merge specific files in provided order
// Concatenate content and write to workspace/merged.txt
let filesToMerge;
try {
const filesArg = parseFilesArg();
if (filesArg) {
// --files mode
filesToMerge = filesArg.map(f => path.join(PARTS_DIR, f));
// Check all files exist
await Promise.all(filesToMerge.map(async file => {
try {
await fs.access(file);
} catch {
throw new Error('FS operation failed');
}
}));
} else {
// Default mode: all .txt files in parts, sorted
let entries;
try {
entries = await fs.readdir(PARTS_DIR, { withFileTypes: true });
} catch {
throw new Error('FS operation failed');
}
filesToMerge = entries
.filter(e => e.isFile() && e.name.endsWith('.txt'))
.map(e => path.join(PARTS_DIR, e.name))
.sort();
if (filesToMerge.length === 0) throw new Error('FS operation failed');
}
// Read and concatenate
const contents = await Promise.all(filesToMerge.map(f => fs.readFile(f, 'utf-8')));
await fs.mkdir(path.dirname(MERGED_FILE), { recursive: true });
await fs.writeFile(MERGED_FILE, contents.join(''));
} catch (e) {
throw new Error('FS operation failed');
}
};

await merge();
await merge();
43 changes: 39 additions & 4 deletions src/fs/restore.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
import fs from 'fs/promises';
import path from 'path';

const SNAPSHOT = path.join(process.cwd(), 'snapshot.json');
const RESTORE_DIR = path.join(process.cwd(), 'workspace_restored');

const restore = async () => {
// Write your code here
// Read snapshot.json
// Treat snapshot.rootPath as metadata only
// Recreate directory/file structure in workspace_restored
// Check if snapshot.json exists
let snapshot;
try {
const data = await fs.readFile(SNAPSHOT, 'utf-8');
snapshot = JSON.parse(data);
} catch (e) {
throw new Error('FS operation failed');
}

// Check if workspace_restored already exists
try {
await fs.access(RESTORE_DIR);
// If no error, directory exists
throw new Error('FS operation failed');
} catch (e) {
if (e.code !== 'ENOENT') throw new Error('FS operation failed');
// else, directory does not exist, continue
}

// Create workspace_restored
await fs.mkdir(RESTORE_DIR);

// Recreate structure
for (const entry of snapshot.entries) {
const dest = path.join(RESTORE_DIR, entry.path);
if (entry.type === 'directory') {
await fs.mkdir(dest, { recursive: true });
} else if (entry.type === 'file') {
await fs.mkdir(path.dirname(dest), { recursive: true });
const content = Buffer.from(entry.content, 'base64');
await fs.writeFile(dest, content);
}
}
};

await restore();
54 changes: 48 additions & 6 deletions src/fs/snapshot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
import fs from 'fs/promises';
import path from 'path';

const WORKSPACE = path.join(process.cwd(), 'workspace');
const SNAPSHOT = path.join(process.cwd(), 'snapshot.json');

async function* walk(dir, base) {
let entries;
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
throw new Error('FS operation failed');
}
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relPath = path.relative(base, fullPath).replace(/\\/g, '/');
if (entry.isDirectory()) {
yield { path: relPath, type: 'directory' };
yield* walk(fullPath, base);
} else if (entry.isFile()) {
const stat = await fs.stat(fullPath);
const content = await fs.readFile(fullPath);
yield {
path: relPath,
type: 'file',
size: stat.size,
content: content.toString('base64')
};
}
}
}

const snapshot = async () => {
// Write your code here
// Recursively scan workspace directory
// Write snapshot.json with:
// - rootPath: absolute path to workspace
// - entries: flat array of relative paths and metadata
// Check if workspace exists
try {
await fs.access(WORKSPACE);
} catch {
throw new Error('FS operation failed');
}
const entries = [];
for await (const entry of walk(WORKSPACE, WORKSPACE)) {
entries.push(entry);
}
const data = {
rootPath: WORKSPACE,
entries
};
await fs.writeFile(SNAPSHOT, JSON.stringify(data, null, 2));
};

await snapshot();
await snapshot();
Loading