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
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ node_modules/
*.log
npm-debug.log*
.env
workspace/
workspace_restored/
snapshot.json
data.json
checksums.json
source.txt
chunk_*.txt
29 changes: 28 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Node.js Fundamentals
# Node.js Fundamentals by Vladimir Tugutov (https://github.com/vladimirtugutov)

## Description

Expand Down Expand Up @@ -62,6 +62,33 @@ Snapshot format reminder:
- `npm run cli:interactive` - Interactive command-line interface
- `npm run cli:progress` - Display progress bar

To run with color use:

#### With color - IMPORTANT: use quotes or escaping!
node src/cli/progress.js --color "#00ff00" # Green
node src/cli/progress.js --color '#0000ff' # Blue
node src/cli/progress.js --color "#ff0000" # Red
node src/cli/progress.js --color "#ffff00" # Yellow

#### Full customization
node src/cli/progress.js \
--duration 3000 \
--interval 50 \
--length 50 \
--color "#00ff00"

#### Windows CMD (quotes not required)
node src\cli\progress.js --color #00ff00

#### npm Scripts:
npm run cli:progress -- --color "#00ff00"

#### DOESN'T WORK: #00ff00 becomes a comment
node src/cli/progress.js --color #00ff00

#### WORKS: quotes protect from shell
node src/cli/progress.js --color "#00ff00"

### Modules (src/modules)

- `npm run modules:dynamic` - Dynamic plugin loading
Expand Down
1 change: 1 addition & 0 deletions data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[5, 1, 9, 3, 7, 2, 8, 4, 6]
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.

21 changes: 21 additions & 0 deletions source.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
line 1
line 2
line 3
line 4
line 5
line 6
line 7
line 8
line 9
line 10
line 11
line 12
line 13
line 14
line 15
line 16
line 17
line 18
line 19
line 20
line 21
4 changes: 4 additions & 0 deletions src/checksums.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"file1.txt": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"file2.txt": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
46 changes: 42 additions & 4 deletions src/cli/interactive.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
import readline from 'node:readline';
import { stdin as input, stdout as output } from 'node:process';

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, output, prompt: '> ' });

const handleCommand = (line) => {
const cmd = line.trim();

switch (cmd) {
case 'uptime':
output.write(`Uptime: ${process.uptime().toFixed(2)}s\n`);
break;
case 'cwd':
output.write(`${process.cwd()}\n`);
break;
case 'date':
output.write(`${new Date().toISOString()}\n`);
break;
case 'exit':
output.write('Goodbye!\n');
rl.close();
return;
default:
output.write('Unknown command\n');
break;
}

rl.prompt();
};

rl.on('line', handleCommand);

rl.on('SIGINT', () => {
output.write('Goodbye!\n');
rl.close();
});

rl.on('close', () => {
process.exit(0);
});

rl.prompt();
};

interactive();
85 changes: 81 additions & 4 deletions src/cli/progress.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,85 @@
import { argv, stdout } from 'node:process';

const parseArgs = () => {
const args = argv.slice(2);
const options = {};

for (let i = 0; i < args.length; i += 2) {
const key = args[i];
const value = args[i + 1];
if (!value && key === '--color') {
stdout.write(
'Hint: use quotes or escaping for color, e.g. --color "#00ff00". If you use npm run script add "-- -- color ..."\n'
);
}
if (!value) continue;

switch (key) {
case '--duration':
options.duration = Number(value);
break;
case '--interval':
options.interval = Number(value);
break;
case '--length':
options.length = Number(value);
break;
case '--color':
options.color = value;
break;
default:
break;
}
}

return options;
};

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

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 = 5000,
interval = 100,
length = 30,
color,
} = parseArgs();

const totalSteps = Math.max(1, Math.floor(duration / interval));
const useColor = typeof color === 'string' && isValidHexColor(color);

let currentStep = 0;

const timer = setInterval(() => {
currentStep += 1;
const fraction = Math.min(1, currentStep / totalSteps);
const percent = Math.round(fraction * 100);

const filledLength = Math.round(length * fraction);
const emptyLength = length - filledLength;

const filled = '█'.repeat(filledLength);
const empty = ' '.repeat(emptyLength);

let bar = `[${filled}${empty}] ${percent}%`;

if (useColor && filledLength > 0) {
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
const colorPrefix = `\x1b[38;2;${r};${g};${b}m`;
const reset = '\x1b[0m';
const coloredFilled = `${colorPrefix}${filled}${reset}`;
bar = `[${coloredFilled}${empty}] ${percent}%`;
}

stdout.write(`\r${bar}`);

if (currentStep >= totalSteps) {
clearInterval(timer);
stdout.write('\nDone!\n');
}
}, interval);
};

progress();
39 changes: 33 additions & 6 deletions src/cp/execCommand.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import { spawn } from 'node:child_process';
import { argv } from 'node: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 args = argv.slice(2);

if (args.length === 0) {
console.error('Usage: node execCommand.js "command [args...]"');
process.exit(1);
}

const command = args.join(' ');

const parts = command.split(' ');
const cmd = parts[0];
const cmdArgs = parts.slice(1);

const child = spawn(cmd, cmdArgs, {
stdio: ['inherit', 'pipe', 'pipe'],
env: { ...process.env }
});

child.stdout.pipe(process.stdout);

child.stderr.pipe(process.stderr);

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

child.on('error', (error) => {
console.error('Spawn error:', error.message);
process.exit(1);
});
};

execCommand();
1 change: 1 addition & 0 deletions src/files/file1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
1 change: 1 addition & 0 deletions src/files/file2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello
56 changes: 53 additions & 3 deletions src/fs/findByExt.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
import { readdir, stat } from 'fs/promises';
import { dirname, join, extname } from 'path';
import { fileURLToPath } from 'url';
import { argv } from 'node:process';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const parseExtArg = () => {
const args = argv.slice(2);
for (let i = 0; i < args.length; i++) {
if (args[i] === '--ext' && args[i + 1]) {
return args[i + 1].startsWith('.') ? args[i + 1] : `.${args[i + 1]}`;
}
}
return '.txt'; // Default
};

const findFilesByExtension = async (dir, ext, relativeBase) => {
const entries = await readdir(dir, { withFileTypes: true });
const files = [];

for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relPath = join(relativeBase, entry.name);

if (entry.isDirectory()) {
const subFiles = await findFilesByExtension(fullPath, ext, relPath);
files.push(...subFiles);
} else if (extname(entry.name) === ext) {
files.push(relPath);
}
}

return files;
};

const findByExt = async () => {
// Write your code here
// Recursively find all files with specific extension
// Parse --ext CLI argument (default: .txt)
const ext = parseExtArg();
const workspacePath = join(__dirname, '..', 'workspace');

try {
const files = await findFilesByExtension(workspacePath, ext, '');
const sortedFiles = files.sort();

for (const file of sortedFiles) {
console.log(file);
}
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error('FS operation failed');
}
throw error;
}
};

await findByExt();
Loading