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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
},
"type": "module",
"scripts": {
"start": "node src/main.js"
"start": "node src/main.js",
"generate-logs": "node scripts/generate-logs.js --output src/commands/logs.txt --lines 500000"
},
"repository": {
"type": "git",
Expand Down
99 changes: 99 additions & 0 deletions scripts/generate-logs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!/usr/bin/env node
"use strict";
import fs from "fs";
import path from "path";
import {parseArgs} from "util";

const {values} = parseArgs({
options: {
output: {type: "string"},
lines: {type: "string", default: "100000"},
seed: {type: "string", default: "123456"},
},
});

const output = values.output;
const lines = Number(values.lines);
const seed = Number(values.seed);

if (!output || !Number.isFinite(lines) || lines <= 0) {
process.stderr.write(
"Usage: node scripts/generate-logs.js --output <path> --lines <count> [--seed <number>]\n",
);
process.exit(1);
}

const levels = ["INFO", "WARN", "ERROR"];
const services = [
"user-service",
"order-service",
"payment-service",
"search-service",
"email-service",
];
const methods = ["GET", "POST", "PUT", "DELETE"];
const paths = [
"/api/users",
"/api/users/:id",
"/api/orders",
"/api/orders/:id",
"/api/payments",
"/api/search",
"/api/login",
"/api/logout",
"/api/health",
];

let state = seed >>> 0;
const rand = () => {
// LCG: deterministic pseudo-random generator
state = (1664525 * state + 1013904223) >>> 0;
return state / 0xffffffff;
};

const pick = (arr) => arr[Math.floor(rand() * arr.length)];

const start = Date.parse("2026-01-01T00:00:00.000Z");
let current = start;

const outPath = path.resolve(process.cwd(), output);
fs.mkdirSync(path.dirname(outPath), {recursive: true});

const stream = fs.createWriteStream(outPath, {encoding: "utf8"});

let written = 0;
const writeBatch = () => {
let ok = true;
while (written < lines && ok) {
const dt = Math.floor(rand() * 5000); // up to 5s
current += dt;
const iso = new Date(current).toISOString();
const level = pick(levels);
const service = pick(services);
const method = pick(methods);
const pathVal = pick(paths);
const statusBase = level === "ERROR" ? 500 : level === "WARN" ? 400 : 200;
const status = statusBase + Math.floor(rand() * 50);
const responseTime = 5 + Math.floor(rand() * 2000);
const line = `${iso} ${level} ${service} ${status} ${responseTime} ${method} ${pathVal}\n`;
ok = stream.write(line);
written += 1;
}

if (written < lines) {
stream.once("drain", writeBatch);
} else {
stream.end();
}
};

stream.on("finish", () => {
process.stdout.write(`Generated ${lines} lines at ${outPath}\n`);
});

stream.on("error", (err) => {
process.stderr.write(`Failed to write logs: ${err.message}\n`);
process.exit(1);
});

writeBatch();
39 changes: 39 additions & 0 deletions src/commands/count.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from "fs";
import path from "path";
import readline from "readline";

export const countFile = async (inputPath) => {
try {
const resolvedInput = path.resolve(process.cwd(), inputPath);

if (!fs.existsSync(resolvedInput)) {
console.log("Operation failed: input file does not exist");
return;
}

let lines = 0;
let words = 0;
let characters = 0;

const readStream = fs.createReadStream(resolvedInput, "utf-8");

const rl = readline.createInterface({
input: readStream,
crlfDelay: Infinity,
});

rl.on("line", (line) => {
lines += 1;
characters += line.length + 1;
words += line.trim().split(/\s+/).filter(Boolean).length;
});

await new Promise((resolve) => rl.on("close", resolve));

console.log(`Lines: ${lines}`);
console.log(`Words: ${words}`);
console.log(`Characters: ${characters}`);
} catch (err) {
console.log("Operation failed:", err.message);
}
};
65 changes: 65 additions & 0 deletions src/commands/csvToJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import fs from "fs";
import path from "path";
import {Transform} from "stream";
import {pipeline} from "stream/promises";

export const csvToJson = async (inputPath, outputPath) => {
try {
const resolvedInput = path.resolve(process.cwd(), inputPath);
const resolvedOutput = path.resolve(process.cwd(), outputPath);

if (!fs.existsSync(resolvedInput)) {
console.log("Operation failed");
return;
}

fs.closeSync(fs.openSync(resolvedOutput, "a"));

const readStream = fs.createReadStream(resolvedInput, "utf-8");
const writeStream = fs.createWriteStream(resolvedOutput);

let headers = [];
let isFirstLine = true;
let isFirstObject = true;

const transform = new Transform({
readableObjectMode: false,
writableObjectMode: false,

transform(chunk, _, callback) {
const inputLines = chunk.toString().split("\n").filter(Boolean);
let output = "";

for (const line of inputLines) {
const values = line.split(",");

if (isFirstLine) {
headers = values;
isFirstLine = false;
output += "[";
continue;
}

const outPutEntity = {};
headers.forEach((h, i) => {
outPutEntity[h.trim()] = values[i]?.trim();
});

if (!isFirstObject) output += ",";
output += JSON.stringify(outPutEntity);
isFirstObject = false;
}

callback(null, output);
},

flush(callback) {
callback(null, "]");
},
});

await pipeline(readStream, transform, writeStream);
} catch {
console.log("Operation failed");
}
};
5 changes: 5 additions & 0 deletions src/commands/data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name,age,city
Alice,30,New York
Bob,25,London
Bob,32,Paris
Mark,27,Munich
65 changes: 65 additions & 0 deletions src/commands/decrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import fs from "fs";
import path from "path";
import crypto from "crypto";
import {pipeline} from "stream";

const HEADER_SIZE = 28;
const AUTH_TAG_SIZE = 16;
const TOTAL_BUFFER_SIZE = HEADER_SIZE + AUTH_TAG_SIZE;

export const decrypt = (input, output, password) => {
try {
if (!input || !output || !password) {
console.log("Operation failed: Missing required parameters");
return;
}

const inputPath = path.resolve(process.cwd(), input);
const outputPath = path.resolve(process.cwd(), output);

if (!fs.existsSync(inputPath)) {
console.log("Operation failed: Input file not found");
return;
}

const stat = fs.statSync(inputPath);

if (stat.size < TOTAL_BUFFER_SIZE) {
console.log("Operation failed: File is too small to be valid");
return;
}

const fd = fs.openSync(inputPath, "r");

const header = Buffer.alloc(HEADER_SIZE);
fs.readSync(fd, header, 0, HEADER_SIZE, 0);

const salt = header.subarray(0, AUTH_TAG_SIZE);
const iv = header.subarray(AUTH_TAG_SIZE, HEADER_SIZE);

const authTag = Buffer.alloc(AUTH_TAG_SIZE);
fs.readSync(fd, authTag, 0, AUTH_TAG_SIZE, stat.size - AUTH_TAG_SIZE);

fs.closeSync(fd);

const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha256");

const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(authTag);

const inputStream = fs.createReadStream(inputPath, {
start: HEADER_SIZE,
end: stat.size - AUTH_TAG_SIZE - 1,
});

const outputStream = fs.createWriteStream(outputPath);

pipeline(inputStream, decipher, outputStream, (err) => {
if (err) {
console.log("Operation failed");
}
});
} catch {
console.log("Operation failed");
}
};
49 changes: 49 additions & 0 deletions src/commands/encrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fs from "fs";
import path from "path";
import crypto from "crypto";
import {pipeline} from "stream";

export const encrypt = (input, output, password) => {
try {
if (!input || !output || !password) {
console.log("Operation failed: Missing required parameters");
return;
}

const inputPath = path.resolve(process.cwd(), input);
const outputPath = path.resolve(process.cwd(), output);

if (!fs.existsSync(inputPath)) {
console.log("Operation failed: Input file not found");
return;
}

fs.closeSync(fs.openSync(outputPath, "a"));

const salt = crypto.randomBytes(16);
const iv = crypto.randomBytes(12);
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, "sha256");
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);

const inputStream = fs.createReadStream(inputPath);
const outputStream = fs.createWriteStream(outputPath);

outputStream.write(Buffer.concat([salt, iv]));

pipeline(inputStream, cipher, outputStream, (err) => {
if (err) {
console.log("Operation failed");
return;
}

const authTag = cipher.getAuthTag();
fs.appendFile(outputPath, authTag, (err) => {
if (err) {
console.log("Operation failed");
}
});
});
} catch {
console.log("Operation failed: An error occurred during encryption");
}
};
18 changes: 18 additions & 0 deletions src/commands/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Random text and words:
apple
banana
cloud
river
mountain
sky
computer
keyboard
mouse
window
door
light
shadow
book
pen
paper
pineapple
Loading