Skip to content
Merged
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
49 changes: 39 additions & 10 deletions src/lib/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,83 @@
import { red } from "ansis";
import { cyan, red } from "ansis";

export class CLIError extends Error {
constructor(message: string) {
public readonly suggestion?: string;

constructor(message: string, suggestion?: string) {
super(message);
this.name = this.constructor.name;
this.suggestion = suggestion;
Object.setPrototypeOf(this, new.target.prototype);
}
}

export class ConfigNotFoundError extends CLIError {
constructor(configPath: string) {
super(
`Configuration file not found at path: ${configPath}. Please run 'nf new' or provide a valid --config path.`,
`Configuration file not found at path: ${configPath}.`,
"Please run 'nf new' or provide a valid --config path.",
);
}
}

export class BuildError extends CLIError {
constructor(details: string) {
super(`Build failed: ${details}`);
super(
`Build failed: ${details}`,
"Check the logs above for syntax errors or configuration issues.",
);
}
}

export class InvalidCommandArgumentError extends CLIError {
constructor(argName: string, expected: string) {
super(`Invalid argument '${argName}'. Expected: ${expected}.`);
super(
`Invalid argument '${argName}'. Expected: ${expected}.`,
"Verify the command syntax using the --help flag.",
);
}
}

export class RegistryAuthenticationError extends CLIError {
constructor() {
super("You must be logged in to perform this action. Run 'nf login'.");
super("You must be logged in to perform this action.", "Run 'nf login' to authenticate.");
}
}

export class ProjectInitializationError extends CLIError {
constructor(details: string) {
super(`Failed to create new project: ${details}`);
super(
`Failed to create new project: ${details}`,
"Verify your permissions and that the target directory is empty.",
);
}
}

export class ManifestError extends CLIError {
constructor(detail: string) {
super(`Manifest Error: ${detail}`);
super(
`Manifest Error: ${detail}`,
"Check your nanoforge.manifest.json file for syntax or formatting errors.",
);
}
}

export class FileSystemError extends CLIError {
constructor(action: string, targetPath: string) {
super(`File System Error [${action}]: ${targetPath}`);
super(
`File System Error [${action}]: ${targetPath}`,
"Verify your file permissions and ensure the path exists.",
);
}
}

export class ApiRequestError extends CLIError {
constructor(status: number, cause?: unknown) {
const causeStr = cause && typeof cause === "object" ? JSON.stringify(cause, null, 2) : cause;
super(`API Request failed (Status ${status})${causeStr ? `\nDetails: ${causeStr}` : ""}`);
super(
`API Request failed (Status ${status})${causeStr ? `\nDetails: ${causeStr}` : ""}`,
"Check your network connection, API key, or the registry status.",
);
}
}

Expand All @@ -77,10 +99,17 @@ export const getErrorMessage = (error: unknown): string | undefined => {
export const handleActionError = (context: string, error: unknown): never => {
console.error();
console.error(red(context));

if (error instanceof CLIError) {
console.error(error.message);

if (error.suggestion) {
console.info(cyan(`\n💡 Suggestion: ${error.suggestion}`));
}

process.exit(1);
}

const msg = getErrorMessage(error);
if (msg) console.error(msg);
process.exit(1);
Expand Down
Loading