kira-crud
Version:
Intelligent CRUD Generator for Laravel and Angular
471 lines (398 loc) • 14 kB
JavaScript
#!/usr/bin/env node
/**
* Transaction Management CLI
* Command line tool for testing and demonstrating the transaction system
*/
const inquirer = require('inquirer');
const chalk = require('chalk');
const ora = require('ora');
const figlet = require('figlet');
const boxen = require('boxen');
const path = require('path');
const fs = require('fs').promises;
const { program } = require('commander');
const gradient = require('gradient-string');
const TransactionManager = require('./utils/transaction-manager');
// Constants for styling
const titleGradient = gradient(['#4285F4', '#34A853']);
/**
* Display the banner
*/
function displayBanner() {
console.log(
titleGradient.multiline(
figlet.textSync('Transaction', {
font: 'Small',
horizontalLayout: 'default'
})
)
);
console.log(
boxen(
`${chalk.bold('KIRA Transaction Manager')} ${chalk.dim('v1.0.0')}\n` +
`Test and demonstrate the transaction rollback system`,
{
padding: 1,
margin: { top: 1, bottom: 1 },
borderStyle: 'round',
borderColor: 'blue'
}
)
);
}
/**
* Create a test transaction
* @param {Object} options - Test options
*/
async function createTestTransaction(options) {
const transaction = new TransactionManager({
verbose: options.verbose,
createBackups: true
});
await transaction.init();
// Create test directory
const testDir = path.join(process.cwd(), 'transaction-test');
await fs.mkdir(testDir, { recursive: true });
// Add operations to the transaction
await transaction.addMkdir(testDir);
// Create a test file
const testFile1 = path.join(testDir, 'test1.txt');
await transaction.addWrite(testFile1, 'This is test file 1');
// Create a second test file
const testFile2 = path.join(testDir, 'test2.txt');
await transaction.addWrite(testFile2, 'This is test file 2');
// Create a subdirectory
const subDir = path.join(testDir, 'subdir');
await transaction.addMkdir(subDir);
// Create a file in the subdirectory
const testFile3 = path.join(subDir, 'test3.txt');
await transaction.addWrite(testFile3, 'This is test file 3');
return { transaction, testDir, files: [testFile1, testFile2, testFile3] };
}
/**
* Run a successful transaction test
* @param {Object} options - Test options
*/
async function runSuccessTest(options) {
const spinner = ora('Creating test transaction...').start();
try {
const { transaction, testDir, files } = await createTestTransaction(options);
spinner.text = 'Executing transaction...';
const success = await transaction.execute();
if (success) {
spinner.succeed('Transaction completed successfully');
// Verify files were created
let allFilesExist = true;
for (const file of files) {
try {
await fs.access(file);
} catch (error) {
allFilesExist = false;
console.error(chalk.red(`File not found: ${file}`));
}
}
if (allFilesExist) {
console.log(chalk.green('All files were created successfully'));
}
// Clean up test files if requested
if (options.cleanup) {
spinner.text = 'Cleaning up test files...';
spinner.start();
await fs.rm(testDir, { recursive: true, force: true });
await transaction.cleanup();
spinner.succeed('Test files cleaned up');
} else {
console.log(chalk.yellow(`Test files remain in: ${testDir}`));
console.log(chalk.yellow(`Backup files in: ${transaction.backupPath}`));
}
} else {
spinner.fail('Transaction failed');
}
} catch (error) {
spinner.fail('Test failed');
console.error(chalk.red(`Error: ${error.message}`));
}
}
/**
* Run a failed transaction test
* @param {Object} options - Test options
*/
async function runFailureTest(options) {
const spinner = ora('Creating test transaction...').start();
try {
const { transaction, testDir, files } = await createTestTransaction(options);
// Add a failing operation
const impossibleDir = '/this/directory/does/not/exist/and/cannot/be/created';
const impossibleFile = path.join(impossibleDir, 'impossible.txt');
await transaction.addWrite(impossibleFile, 'This should fail');
spinner.text = 'Executing transaction (expected to fail)...';
const success = await transaction.execute();
if (success) {
spinner.fail('Transaction succeeded unexpectedly');
} else {
spinner.succeed('Transaction failed as expected');
// Check if rollback was successful
let allFilesRemoved = true;
for (const file of files) {
try {
await fs.access(file);
allFilesRemoved = false;
console.error(chalk.red(`File still exists: ${file}`));
} catch (error) {
// File should not exist after rollback, so this is good
}
}
if (allFilesRemoved) {
console.log(chalk.green('All files were properly rolled back'));
} else {
console.log(chalk.red('Some files were not rolled back properly'));
}
// Clean up test directory if it still exists
try {
await fs.access(testDir);
if (options.cleanup) {
spinner.text = 'Cleaning up test directory...';
spinner.start();
await fs.rm(testDir, { recursive: true, force: true });
await transaction.cleanup();
spinner.succeed('Test directory cleaned up');
} else {
console.log(chalk.yellow(`Test directory remains: ${testDir}`));
console.log(chalk.yellow(`Backup directory: ${transaction.backupPath}`));
}
} catch (error) {
// Directory doesn't exist, which is expected after rollback
}
}
} catch (error) {
spinner.fail('Test failed unexpectedly');
console.error(chalk.red(`Error: ${error.message}`));
}
}
/**
* Run an interactive transaction test
*/
async function runInteractiveTest() {
displayBanner();
const { testType } = await inquirer.prompt({
type: 'list',
name: 'testType',
message: 'Select a test to run:',
choices: [
{ name: 'Successful Transaction', value: 'success' },
{ name: 'Failed Transaction with Rollback', value: 'failure' },
{ name: 'Custom Transaction', value: 'custom' }
]
});
const options = await inquirer.prompt([
{
type: 'confirm',
name: 'verbose',
message: 'Enable verbose output?',
default: true
},
{
type: 'confirm',
name: 'cleanup',
message: 'Clean up test files after completion?',
default: true
}
]);
if (testType === 'success') {
await runSuccessTest(options);
} else if (testType === 'failure') {
await runFailureTest(options);
} else if (testType === 'custom') {
await runCustomTest(options);
}
}
/**
* Run a custom transaction test
* @param {Object} options - Test options
*/
async function runCustomTest(options) {
console.log(chalk.blue('\nCustom Transaction Test'));
console.log(chalk.blue('------------------------'));
const transaction = new TransactionManager({
verbose: options.verbose,
createBackups: true
});
await transaction.init();
// Create test directory
const testDir = path.join(process.cwd(), 'custom-transaction-test');
await fs.mkdir(testDir, { recursive: true });
await transaction.addMkdir(testDir);
let keepAdding = true;
const operations = [];
while (keepAdding) {
const { operationType } = await inquirer.prompt({
type: 'list',
name: 'operationType',
message: 'Add operation:',
choices: [
{ name: 'Write File', value: 'write' },
{ name: 'Create Directory', value: 'mkdir' },
{ name: 'Delete File', value: 'delete' },
{ name: 'Force Failure', value: 'fail' },
{ name: 'Execute Transaction', value: 'execute' }
]
});
if (operationType === 'execute') {
keepAdding = false;
} else if (operationType === 'write') {
const { fileName, content } = await inquirer.prompt([
{
type: 'input',
name: 'fileName',
message: 'File name:',
default: `file${operations.length + 1}.txt`
},
{
type: 'input',
name: 'content',
message: 'File content:',
default: `This is test file ${operations.length + 1}`
}
]);
const filePath = path.join(testDir, fileName);
await transaction.addWrite(filePath, content);
operations.push({ type: 'write', path: filePath });
console.log(chalk.green(`Added write operation for: ${filePath}`));
} else if (operationType === 'mkdir') {
const { dirName } = await inquirer.prompt({
type: 'input',
name: 'dirName',
message: 'Directory name:',
default: `dir${operations.length + 1}`
});
const dirPath = path.join(testDir, dirName);
await transaction.addMkdir(dirPath);
operations.push({ type: 'mkdir', path: dirPath });
console.log(chalk.green(`Added mkdir operation for: ${dirPath}`));
} else if (operationType === 'delete') {
const { fileName } = await inquirer.prompt({
type: 'input',
name: 'fileName',
message: 'File to delete:',
default: 'file-to-delete.txt'
});
// Create the file first so we can delete it
const filePath = path.join(testDir, fileName);
await fs.writeFile(filePath, 'This file will be deleted');
await transaction.addDelete(filePath);
operations.push({ type: 'delete', path: filePath });
console.log(chalk.green(`Added delete operation for: ${filePath}`));
} else if (operationType === 'fail') {
const impossibleDir = '/this/directory/does/not/exist';
const impossibleFile = path.join(impossibleDir, 'impossible.txt');
await transaction.addWrite(impossibleFile, 'This should fail');
operations.push({ type: 'write', path: impossibleFile });
console.log(chalk.yellow(`Added failing operation for: ${impossibleFile}`));
}
}
const spinner = ora('Executing transaction...').start();
try {
const success = await transaction.execute();
if (success) {
spinner.succeed('Transaction completed successfully');
// Verify files and directories
for (const operation of operations) {
if (operation.type === 'write') {
try {
await fs.access(operation.path);
console.log(chalk.green(`File created: ${operation.path}`));
} catch (error) {
console.log(chalk.red(`File not found: ${operation.path}`));
}
} else if (operation.type === 'mkdir') {
try {
await fs.access(operation.path);
console.log(chalk.green(`Directory created: ${operation.path}`));
} catch (error) {
console.log(chalk.red(`Directory not found: ${operation.path}`));
}
} else if (operation.type === 'delete') {
try {
await fs.access(operation.path);
console.log(chalk.red(`File still exists: ${operation.path}`));
} catch (error) {
console.log(chalk.green(`File deleted: ${operation.path}`));
}
}
}
} else {
spinner.fail('Transaction failed');
// Check rollback status
console.log(chalk.yellow('\nChecking rollback status:'));
for (const operation of operations) {
if (operation.type === 'write') {
try {
await fs.access(operation.path);
console.log(chalk.red(`File still exists: ${operation.path}`));
} catch (error) {
console.log(chalk.green(`File properly rolled back: ${operation.path}`));
}
}
}
}
// Clean up test directory if requested
if (options.cleanup) {
spinner.text = 'Cleaning up test files...';
spinner.start();
await fs.rm(testDir, { recursive: true, force: true });
await transaction.cleanup();
spinner.succeed('Test files cleaned up');
} else {
console.log(chalk.yellow(`\nTest files remain in: ${testDir}`));
console.log(chalk.yellow(`Backup files in: ${transaction.backupPath}`));
}
} catch (error) {
spinner.fail('Test failed unexpectedly');
console.error(chalk.red(`Error: ${error.message}`));
}
}
/**
* Command line interface setup
*/
program
.version('1.0.0')
.description('KIRA Transaction Manager Test Tool')
.option('-s, --success', 'Run a successful transaction test')
.option('-f, --failure', 'Run a transaction test with failure and rollback')
.option('-v, --verbose', 'Enable verbose output')
.option('-c, --cleanup', 'Clean up test files after completion')
.option('-i, --interactive', 'Run interactive test');
program.parse(process.argv);
/**
* Main application entry point
*/
async function main() {
const options = program.opts();
// Default to interactive mode if no specific options provided
if (!options.success && !options.failure && !options.interactive) {
options.interactive = true;
}
try {
if (options.interactive) {
await runInteractiveTest();
} else if (options.success) {
displayBanner();
await runSuccessTest(options);
} else if (options.failure) {
displayBanner();
await runFailureTest(options);
}
} catch (error) {
console.error(chalk.red(`\nAn error occurred: ${error.message}\n`));
process.exit(1);
}
}
// Run the application
if (require.main === module) {
main();
}
module.exports = {
runSuccessTest,
runFailureTest,
runCustomTest
};