Skip to content

Commit

Permalink
feat: improved error handling and logging
Browse files Browse the repository at this point in the history
  • Loading branch information
ITZSHOAIB committed Sep 11, 2024
1 parent 33a53a6 commit 687849c
Show file tree
Hide file tree
Showing 7 changed files with 153 additions and 124 deletions.
138 changes: 70 additions & 68 deletions src/cucumber/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,95 +1,97 @@
import { execSync, spawn } from "child_process";
import commandExists from "command-exists";
import { log } from "../internal/utils/logger.js";
import { CustomError, handleError } from "../internal/utils/errorHandler.js";

let anvilProcess: any;

export const beforeAll = async () => {
log("info", "🚀 Starting test environment...");
try {
log("info", "🚀 Starting test environment...");

const forgeExist = commandExists.sync("forge");
if (!forgeExist) {
log(
"error",
"❌ Forge is not installed. Please install it first. Please refer to the documentation for more information.\n\nhttps://github.com/ITZSHOAIB/chukti#readme"
);
}
const forgeExist = commandExists.sync("forge");
if (!forgeExist) {
throw new CustomError(
"Forge is not installed. Please install it first. Refer to the documentation: https://github.com/ITZSHOAIB/chukti#readme"
);
}

execSync("forge build", { stdio: "inherit" });

execSync("forge build", { stdio: "inherit" });
const isAnvilExist = commandExists.sync("anvil");
if (!isAnvilExist) {
throw new CustomError(
"Anvil is not installed. Please install it first. Refer to the documentation: https://github.com/ITZSHOAIB/chukti#readme"
);
}

const isAnvilExist = commandExists.sync("anvil");
if (!isAnvilExist) {
log(
"error",
"❌ Anvil is not installed. Please install it first. Please refer to the documentation for more information.\n\nhttps://github.com/ITZSHOAIB/chukti#readme"
);
process.exit(1);
await startAnvil();
} catch (error) {
handleError(error as Error);
}
};

const startAnvil = async (
retries: number = 5,
delay: number = 1000
): Promise<void> => {
return new Promise<void>((resolve, reject) => {
export const afterAll = () => {
if (anvilProcess) {
anvilProcess.kill();
anvilProcess.stdout.removeAllListeners();
anvilProcess.stderr.removeAllListeners();
anvilProcess.removeAllListeners();
log("info", "Local blockchain stopped");
}
};

const startAnvil = async (
retries: number = 5,
delay: number = 1000
): Promise<void> => {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
anvilProcess = spawn("anvil", { stdio: "pipe" });

let stdio = "";
let stderr = "";

const onData = (data: any) => {
anvilProcess.stdout.on("data", (data: Buffer) => {
stdio += data.toString();
if (stdio.includes("Listening on")) {
cleanup();
resolve();
log("success", "✅ Anvil started successfully");
return;
}
};
});

const onError = (data: any) => {
anvilProcess.stderr.on("data", (data: Buffer) => {
stderr += data.toString();
log(
"error",
`❌ Error while starting Local blockchain anvil:
stdout: ${stdio}
stderr: ${stderr}`
);
if (retries > 0) {
log(
"info",
`🔄 Retrying to start anvil... (${retries} retries left)`
);
setTimeout(
() =>
startAnvil(retries - 1, delay * 2)
.then(resolve)
.catch(reject),
delay
});

anvilProcess.on("close", (code: number) => {
if (code !== 0) {
throw new CustomError(
`Anvil process exited with code ${code}: ${stderr}`
);
} else {
cleanup();
reject(new Error("Failed to start anvil after multiple attempts"));
}
};

const onClose = () => {
log("info", "Local blockchain exited");
};

const cleanup = () => {
anvilProcess.stdout.off("data", onData);
anvilProcess.stderr.off("data", onError);
anvilProcess.off("close", onClose);
};

anvilProcess.stdout.on("data", onData);
anvilProcess.stderr.on("data", onError);
anvilProcess.on("close", onClose);
});
};
});

await startAnvil();
};
await new Promise((resolve) => setTimeout(resolve, delay));

export const afterAll = () => {
anvilProcess.kill();
log("info", "Local blockchain stopped");
if (stdio.includes("Listening on")) {
return;
} else {
throw new CustomError("Anvil did not start successfully");
}
} catch (error) {
log(
"warning",
`⚠️ Attempt ${attempt} to start Anvil failed: ${
(error as Error).message
}`
);
if (attempt === retries) {
handleError(
new CustomError(`Failed to start Anvil after ${retries} attempts`)
);
}
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
};
2 changes: 1 addition & 1 deletion src/cucumber/steps/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const deployContract = async function (
});

log(
"sucess",
"success",
`✅ Deployed contract ${contractName}.sol with hash ${transactionHash}`
);
};
38 changes: 21 additions & 17 deletions src/internal/cli/handlers/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from "fs-extra";
import { checkChuktiProject } from "../utils/helpers.js";
import { log } from "../../utils/logger.js";
import { execSync } from "child_process";
import { handleError, CustomError } from "../../utils/errorHandler.js";

export const initProject = async () => {
try {
Expand All @@ -11,28 +12,31 @@ export const initProject = async () => {
checkChuktiProject({ shouldExist: false });
await proceedWithInitialization();
} catch (error) {
log("error", `❌ Error during initialization: ${(error as Error).message}`);
process.exit(1);
handleError(error as Error);
}
};

const proceedWithInitialization = async () => {
const currentDir = process.cwd();
const templateDir = path.join(
__dirname,
"../../../../sample-projects/anvil-cucumber"
);
try {
const currentDir = process.cwd();
const templateDir = path.join(
__dirname,
"../../../../sample-projects/anvil-cucumber"
);

// Copy the template files to the current directory
log("info", "📁 Copying template files...");
fs.copySync(templateDir, currentDir);
// Copy the template files to the current directory
log("info", "📁 Copying template files...");
fs.copySync(templateDir, currentDir);

log("sucess", "✅ Project initialized successfully");
log("success", "✅ Project initialized successfully");

// Install the dependencies
log("info", "📦 Installing dependencies...");
execSync("npm install", { stdio: "inherit" });
log("info", "📦 Installing chukti...");
execSync("npm install -D chukti", { stdio: "inherit" });
log("sucess", "✅ Dependencies installed successfully");
// Install the dependencies
log("info", "📦 Installing dependencies...");
execSync("npm install", { stdio: "inherit" });
log("info", "📦 Installing chukti...");
execSync("npm install -D chukti", { stdio: "inherit" });
log("success", "✅ Dependencies installed successfully");
} catch (error) {
throw new CustomError((error as Error).message);
}
};
8 changes: 5 additions & 3 deletions src/internal/cli/handlers/runTests.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { execSync } from "child_process";
import { log } from "../../utils/logger.js";
import { CustomError, handleError } from "../../utils/errorHandler.js";

export const runTests = async () => {
try {
log("info", "🚀 Running Cucumber tests...");
execSync("npx cucumber-js", { stdio: "inherit" });
log("sucess", "✅ Tests completed successfully");
log("success", "✅ Tests completed successfully");
} catch (error) {
log("error", `❌ Error during tests: ${(error as Error).message}`);
process.exit(1);
handleError(
new CustomError(`Error before running tests: ${(error as Error).message}`)
);
}
};
56 changes: 28 additions & 28 deletions src/internal/cli/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
import fs from "fs-extra";
import path from "path";
import { log } from "../../utils/logger.js";
import { CustomError, handleError } from "../../utils/errorHandler.js";

export const checkChuktiProject = ({
shouldExist,
}: {
shouldExist: boolean;
}) => {
const chuktiConfigPath = path.join(process.cwd(), "chukti.config.ts");

const projectExists = fs.existsSync(chuktiConfigPath);

if (shouldExist && !projectExists) {
log(
"error",
"❌ Error: A Chukti project does not exist in this directory.\n\nPlease run `chukti init` to initialize a new project."
);

process.exit(1);
}

if (!shouldExist && projectExists) {
log(
"error",
"❌ Error: A Chukti project already exists in this directory."
);
try {
const chuktiConfigPath = path.join(process.cwd(), "chukti.config.ts");
const projectExists = fs.existsSync(chuktiConfigPath);

if (shouldExist && !projectExists) {
throw new CustomError(
"A Chukti project does not exist in this directory. Please run `chukti init` to initialize a new project."
);
}

process.exit(1);
if (!shouldExist && projectExists) {
throw new CustomError(
"A Chukti project already exists in this directory."
);
}
} catch (error) {
handleError(error as Error);
}
};

export const checkEveryPathExists = (paths: string[]) => {
const errorMessages: string[] = [];
try {
const errorMessages: string[] = [];

for (const p of paths) {
if (!fs.existsSync(p)) {
errorMessages.push(`❌ Error: ${p} does not exist.`);
for (const p of paths) {
if (!fs.existsSync(p)) {
errorMessages.push(`❌ Error: ${p} does not exist.`);
}
}
}

if (errorMessages.length > 0) {
log("error", errorMessages.join("\n"));
process.exit(1);
if (errorMessages.length > 0) {
throw new CustomError(errorMessages.join("\n"));
}
} catch (error) {
handleError(error as Error);
}
};
13 changes: 13 additions & 0 deletions src/internal/utils/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { log } from "./logger.js";

export class CustomError extends Error {
constructor(message: string) {
super(message);
this.name = this.constructor.name;
}
}

export const handleError = (error: Error) => {
log("error", `❌ Error: ${error.message}`);
process.exit(1);
};
22 changes: 15 additions & 7 deletions src/internal/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import kleur from "kleur";

type LogType = "sucess" | "error" | "warning" | "info";
type LogType = "success" | "error" | "warning" | "info";

export const log = (type: LogType, message: string) => {
const formattedMessage = message.replace(/(https?:\/\/[^\s]+)/g, (url) =>
makeUrlClickable(url)
);

switch (type) {
case "sucess":
console.log(kleur.green(message));
case "success":
console.log(kleur.green(formattedMessage));
break;
case "error":
console.error(kleur.red(message));
console.error(kleur.red(formattedMessage));
break;
case "warning":
console.warn(kleur.yellow(message));
console.warn(kleur.yellow(formattedMessage));
break;
case "info":
console.info(kleur.cyan(message));
console.info(kleur.cyan(formattedMessage));
break;
default:
console.log(message);
console.log(formattedMessage);
}
};

const makeUrlClickable = (url: string) => {
return `\u001b]8;;${url}\u001b\\${url}\u001b]8;;\u001b\\`;
};

0 comments on commit 687849c

Please sign in to comment.