@abix/zisk-dev-cli
Version:
A comprehensive CLI tool for ZisK zkVM development with full macOS support. Features project initialization, building, execution, proof generation, environment diagnostics, and project management. This is a personal tool for testing and learning - not an
609 lines (523 loc) • 14.3 kB
JavaScript
/**
* Error Handling System
* Provides structured error handling and recovery mechanisms
*/
class ZiskError extends Error {
constructor(message, context = {}) {
super(message);
this.name = this.constructor.name;
this.context = context;
this.timestamp = new Date().toISOString();
this.code = context.code || 'UNKNOWN_ERROR';
}
/**
* Get formatted error message
*/
getFormattedMessage() {
return `${this.name}: ${this.message}`;
}
/**
* Get error context for debugging
*/
getContext() {
return {
name: this.name,
message: this.message,
code: this.code,
timestamp: this.timestamp,
context: this.context
};
}
}
class SystemError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'SYSTEM_ERROR' });
}
}
class ConfigurationError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'CONFIGURATION_ERROR' });
}
}
class BuildError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'BUILD_ERROR' });
}
}
class ExecutionError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'EXECUTION_ERROR' });
}
}
class ProvingError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'PROVING_ERROR' });
}
}
class NetworkError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'NETWORK_ERROR' });
}
}
class PlatformError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'PLATFORM_ERROR' });
}
}
class ValidationError extends ZiskError {
constructor(message, context = {}) {
super(message, { ...context, code: 'VALIDATION_ERROR' });
}
}
/**
* Error Context Collector
* Collects comprehensive context information for error reporting
*/
class ErrorContextCollector {
constructor() {
this.context = {};
}
/**
* Collect comprehensive error context
*/
async collectContext(error, command, options) {
const context = {
// Error details
error: {
message: error.message,
stack: error.stack,
type: error.constructor.name,
code: error.code || 'UNKNOWN_ERROR'
},
// Command context with sanitized sensitive data
command: {
name: command?.name,
args: this.sanitizeArgs(command?.args || []),
options: this.sanitizeOptions(options || {}),
workingDirectory: this.sanitizePath(process.cwd()),
startTime: command?.startTime,
duration: command?.startTime ? Date.now() - command.startTime : null
},
// System context
system: await this.collectSystemContext(),
// File system context
files: await this.collectFileContext(),
// Process context
process: this.collectProcessContext()
};
return context;
}
/**
* Sanitize command arguments to prevent sensitive data exposure
* @param {Array} args - Command arguments
* @returns {Array} Sanitized arguments
*/
sanitizeArgs(args) {
return args.map(arg => {
if (typeof arg !== 'string') return arg;
// Redact sensitive patterns
if (arg.includes('--proving-key') || arg.includes('--witness') ||
arg.includes('--secret') || arg.includes('--key')) {
return '[REDACTED]';
}
// Redact paths that might contain sensitive information
if (arg.includes('/.ssh/') || arg.includes('/.zisk/') ||
arg.includes('password') || arg.includes('token')) {
return '[REDACTED]';
}
return arg;
});
}
/**
* Sanitize options to prevent sensitive data exposure
* @param {Object} options - Command options
* @returns {Object} Sanitized options
*/
sanitizeOptions(options) {
const sanitized = {};
for (const [key, value] of Object.entries(options)) {
if (typeof value === 'string' &&
(value.includes('--proving-key') || value.includes('--witness') ||
value.includes('/.ssh/') || value.includes('/.zisk/'))) {
sanitized[key] = '[REDACTED]';
} else {
sanitized[key] = value;
}
}
return sanitized;
}
/**
* Sanitize path to prevent directory traversal exposure
* @param {string} pathValue - Path to sanitize
* @returns {string} Sanitized path
*/
sanitizePath(pathValue) {
if (typeof pathValue !== 'string') return String(pathValue);
// Only show relative path from project root
const projectRoot = process.cwd();
if (pathValue.startsWith(projectRoot)) {
return pathValue.substring(projectRoot.length + 1) || '.';
}
return path.basename(pathValue);
}
/**
* Collect system context information
*/
async collectSystemContext() {
const os = require('os');
return {
platform: os.platform(),
arch: os.arch(),
nodeVersion: process.version,
memory: process.memoryUsage(),
uptime: os.uptime(),
loadavg: os.loadavg(),
freemem: os.freemem(),
totalmem: os.totalmem(),
cpus: os.cpus().length
};
}
/**
* Collect file system context
*/
async collectFileContext() {
const fs = require('fs-extra');
const path = require('path');
const files = {
expected: [],
actual: [],
missing: [],
unexpected: []
};
// Check expected files exist
const expectedFiles = this.getExpectedFiles();
for (const filePath of expectedFiles) {
if (await fs.pathExists(filePath)) {
const stats = await fs.stat(filePath);
files.actual.push({
path: filePath,
size: stats.size,
mtime: stats.mtime,
mode: stats.mode
});
} else {
files.missing.push(filePath);
}
}
return files;
}
/**
* Get list of expected files for current project
*/
getExpectedFiles() {
const expectedFiles = [
'Cargo.toml',
'src/main.rs',
'inputs/',
'outputs/'
];
// Add platform-specific files
const { PlatformManager } = require('./platform');
const platform = new PlatformManager();
if (platform.ziskPaths) {
expectedFiles.push(
platform.ziskPaths.provingKey,
platform.ziskPaths.bin
);
}
return expectedFiles;
}
/**
* Collect process context information
*/
collectProcessContext() {
return {
pid: process.pid,
ppid: process.ppid,
argv: process.argv,
env: this.getRelevantEnv(),
cwd: process.cwd(),
memoryUsage: process.memoryUsage(),
uptime: process.uptime()
};
}
/**
* Get relevant environment variables
*/
getRelevantEnv() {
const relevantVars = [
'PATH',
'HOME',
'USER',
'SHELL',
'NODE_ENV',
'ZISK_DEV_DEBUG',
'ZISK_DEV_VERBOSE'
];
const env = {};
for (const varName of relevantVars) {
if (process.env[varName]) {
env[varName] = process.env[varName];
}
}
return env;
}
}
/**
* Error Recovery Manager
* Handles error recovery and cleanup operations
*/
class RecoveryManager {
constructor() {
this.cleanupTasks = [];
this.snapshots = new Map();
}
/**
* Register cleanup task
*/
registerCleanupTask(task) {
this.cleanupTasks.push(task);
}
/**
* Create system snapshot
*/
async createSnapshot(name) {
const snapshot = {
timestamp: Date.now(),
files: await this.captureFileState(),
processes: await this.captureProcessState(),
environment: process.env
};
this.snapshots.set(name, snapshot);
}
/**
* Capture current file state
*/
async captureFileState() {
const fs = require('fs-extra');
const path = require('path');
const files = {};
const directories = ['.zisk-build', 'outputs', 'inputs'];
for (const dir of directories) {
if (await fs.pathExists(dir)) {
const stats = await fs.stat(dir);
files[dir] = {
exists: true,
size: stats.size,
mtime: stats.mtime
};
} else {
files[dir] = { exists: false };
}
}
return files;
}
/**
* Capture current process state
*/
async captureProcessState() {
const { execSync } = require('child_process');
try {
const processes = execSync('ps aux | grep zisk', { encoding: 'utf8' });
return processes.split('\n').filter(line => line.trim());
} catch {
return [];
}
}
/**
* Attempt recovery from failure
*/
async recoverFromFailure(error) {
console.log('Attempting recovery from failure...');
// 1. Stop any running processes
await this.killBackgroundProcesses();
// 2. Clean up temporary files
await this.runCleanupTasks();
// 3. Check if we can retry
const canRetry = await this.assessRetryViability(error);
if (canRetry) {
console.log('Recovery suggestions:');
const suggestions = this.generateRecoverySuggestions(error);
suggestions.forEach(suggestion => {
console.log(` - ${suggestion}`);
});
}
return canRetry;
}
/**
* Kill background processes
*/
async killBackgroundProcesses() {
const { execSync } = require('child_process');
try {
// Use specific pattern matching to avoid killing unintended processes
execSync('pkill -f "cargo-zisk|ziskemu" || true', { stdio: 'ignore' });
} catch (error) {
// Log actual error instead of ignoring
console.warn('Failed to kill background processes:', error.message);
}
}
/**
* Run registered cleanup tasks
*/
async runCleanupTasks() {
for (const task of this.cleanupTasks) {
try {
await task();
} catch (error) {
console.error('Cleanup task failed:', error.message);
}
}
}
/**
* Assess if retry is viable
*/
async assessRetryViability(error) {
// Check if error is retryable
const retryableErrors = [
'NETWORK_ERROR',
'TEMPORARY_FAILURE',
'RESOURCE_UNAVAILABLE'
];
if (retryableErrors.includes(error.code)) {
return true;
}
// Check system resources
const os = require('os');
const freeMem = os.freemem();
const minMemory = 2 * 1024 * 1024 * 1024; // 2GB
if (freeMem < minMemory) {
return false;
}
return true;
}
/**
* Generate recovery suggestions
*/
generateRecoverySuggestions(error) {
const suggestions = [];
switch (error.code) {
case 'NETWORK_ERROR':
suggestions.push('Check internet connection');
suggestions.push('Verify ZISK repository accessibility');
suggestions.push('Try using a different network');
break;
case 'BUILD_ERROR':
suggestions.push('Clean build artifacts: zisk-dev clean');
suggestions.push('Check Rust toolchain: rustup show');
suggestions.push('Verify Cargo.toml configuration');
break;
case 'EXECUTION_ERROR':
suggestions.push('Check input file format');
suggestions.push('Verify ZISK installation: zisk-dev doctor');
suggestions.push('Try with smaller input data');
break;
case 'PROVING_ERROR':
suggestions.push('Check available memory');
suggestions.push('Verify proving key installation');
suggestions.push('Try with reduced parallelism');
break;
case 'SYSTEM_ERROR':
suggestions.push('Check system resources');
suggestions.push('Verify platform compatibility');
suggestions.push('Restart terminal/IDE');
break;
default:
suggestions.push('Check logs for detailed error information');
suggestions.push('Run system diagnostics: zisk-dev doctor');
suggestions.push('Consider reinstalling ZISK: zisk-dev install --force');
}
return suggestions;
}
}
/**
* Error Handler
* Main error handling interface
*/
class ErrorHandler {
constructor() {
this.contextCollector = new ErrorContextCollector();
this.recoveryManager = new RecoveryManager();
}
/**
* Handle error with comprehensive context collection
*/
async handleError(error, command = null, options = {}) {
// Collect error context
const context = await this.contextCollector.collectContext(error, command, options);
// Log error with context
const { Logger } = require('./logger');
const logger = new Logger();
logger.error(error.message, error);
// Attempt recovery
const canRetry = await this.recoveryManager.recoverFromFailure(error);
// Return error context for further handling
return {
error,
context,
canRetry,
suggestions: this.recoveryManager.generateRecoverySuggestions(error)
};
}
/**
* Create error from context
*/
createError(type, message, context = {}) {
const errorClasses = {
SystemError,
ConfigurationError,
BuildError,
ExecutionError,
ProvingError,
NetworkError,
PlatformError,
ValidationError
};
const ErrorClass = errorClasses[type] || ZiskError;
return new ErrorClass(message, context);
}
/**
* Validate error is handled
*/
isHandled(error) {
return error instanceof ZiskError;
}
/**
* Get error type from standard error
*/
classifyError(error) {
if (error.code === 'ENOENT') {
return 'SystemError';
}
if (error.code === 'EACCES') {
return 'SystemError';
}
if (error.message.includes('network')) {
return 'NetworkError';
}
if (error.message.includes('build')) {
return 'BuildError';
}
if (error.message.includes('execution')) {
return 'ExecutionError';
}
if (error.message.includes('proof')) {
return 'ProvingError';
}
return 'SystemError';
}
}
module.exports = {
ZiskError,
SystemError,
ConfigurationError,
BuildError,
ExecutionError,
ProvingError,
NetworkError,
PlatformError,
ValidationError,
ErrorContextCollector,
RecoveryManager,
ErrorHandler
};