8f52722d56
Co-Authored-By: Nellie Mullane <nellie@keygraph.io>
136 lines
4.8 KiB
JavaScript
136 lines
4.8 KiB
JavaScript
import { fs, path, os } from 'zx';
|
|
import chalk from 'chalk';
|
|
import { PentestError, logError } from '../error-handling.js';
|
|
|
|
// Pure function: Save deliverables permanently to user directory
|
|
export async function savePermanentDeliverables(sourceDir, webUrl, repoPath, session, timingBreakdown, costBreakdown) {
|
|
try {
|
|
// Simple universal approach - try Documents, fallback to home
|
|
const homeDir = os.homedir();
|
|
const documentsDir = path.join(homeDir, 'Documents');
|
|
|
|
// Use Documents if it exists, otherwise use home directory
|
|
const baseDir = await fs.pathExists(documentsDir) ? documentsDir : homeDir;
|
|
const permanentBaseDir = path.join(baseDir, 'pentest-deliverables');
|
|
|
|
// Generate directory name from repo path and web URL
|
|
const repoName = path.basename(repoPath);
|
|
const webDomain = new URL(webUrl).hostname.replace(/[^a-zA-Z0-9-]/g, '-');
|
|
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace(/T/, '-').split('.')[0];
|
|
const dirName = `${webDomain}_${repoName}_${timestamp}`;
|
|
const permanentDir = path.join(permanentBaseDir, dirName);
|
|
|
|
// Ensure base directory exists
|
|
await fs.ensureDir(permanentBaseDir);
|
|
|
|
// Create the specific pentest directory
|
|
await fs.ensureDir(permanentDir);
|
|
|
|
// Copy deliverables folder if it exists
|
|
const deliverablesSource = path.join(sourceDir, 'deliverables');
|
|
const deliverablesDest = path.join(permanentDir, 'deliverables');
|
|
|
|
if (await fs.pathExists(deliverablesSource)) {
|
|
await fs.copy(deliverablesSource, deliverablesDest, { overwrite: true });
|
|
}
|
|
|
|
// Save metadata with session information
|
|
const metadata = {
|
|
session: {
|
|
id: session.id,
|
|
webUrl,
|
|
repoPath,
|
|
configFile: session.configFile,
|
|
status: session.status,
|
|
completedAgents: session.completedAgents,
|
|
createdAt: session.createdAt,
|
|
completedAt: new Date().toISOString()
|
|
},
|
|
timing: timingBreakdown,
|
|
cost: costBreakdown,
|
|
sourceDirectory: sourceDir,
|
|
savedAt: new Date().toISOString()
|
|
};
|
|
|
|
await fs.writeJSON(path.join(permanentDir, 'metadata.json'), metadata, { spaces: 2 });
|
|
|
|
// Copy prompts directory for reproducibility
|
|
const promptsSource = path.join(import.meta.dirname, '..', '..', 'prompts');
|
|
const promptsDest = path.join(permanentDir, 'prompts');
|
|
|
|
if (await fs.pathExists(promptsSource)) {
|
|
await fs.copy(promptsSource, promptsDest, { overwrite: true });
|
|
}
|
|
|
|
console.log(chalk.green(`✅ Deliverables saved to permanent location: ${permanentDir}`));
|
|
return permanentDir;
|
|
} catch (error) {
|
|
// Non-fatal error - log but don't throw
|
|
console.log(chalk.yellow(`⚠️ Failed to save permanent deliverables: ${error.message}`));
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Pure function: Save run metadata for debugging and reproducibility
|
|
export async function saveRunMetadata(sourceDir, webUrl, repoPath) {
|
|
console.log(chalk.blue('💾 Saving run metadata...'));
|
|
|
|
try {
|
|
// Read package.json to get version info with error handling
|
|
const packagePath = path.join(import.meta.dirname, '..', '..', 'package.json');
|
|
let packageJson;
|
|
try {
|
|
packageJson = await fs.readJSON(packagePath);
|
|
} catch (packageError) {
|
|
throw new PentestError(
|
|
`Cannot read package.json: ${packageError.message}`,
|
|
'filesystem',
|
|
false,
|
|
{ packagePath, originalError: packageError.message }
|
|
);
|
|
}
|
|
|
|
const metadata = {
|
|
timestamp: new Date().toISOString(),
|
|
targets: { webUrl, repoPath },
|
|
environment: {
|
|
nodeVersion: process.version,
|
|
platform: process.platform,
|
|
arch: process.arch,
|
|
cwd: process.cwd()
|
|
},
|
|
dependencies: {
|
|
claudeCodeVersion: packageJson.dependencies?.['@anthropic-ai/claude-code'] || 'unknown',
|
|
zxVersion: packageJson.dependencies?.['zx'] || 'unknown',
|
|
chalkVersion: packageJson.dependencies?.['chalk'] || 'unknown'
|
|
},
|
|
execution: {
|
|
args: process.argv,
|
|
env: {
|
|
PLAYWRIGHT_HEADLESS: process.env.PLAYWRIGHT_HEADLESS || 'true',
|
|
NODE_ENV: process.env.NODE_ENV
|
|
}
|
|
}
|
|
};
|
|
|
|
const metadataPath = path.join(sourceDir, 'run-metadata.json');
|
|
await fs.writeJSON(metadataPath, metadata, { spaces: 2 });
|
|
|
|
console.log(chalk.green(`✅ Run metadata saved to: ${metadataPath}`));
|
|
return metadata;
|
|
} catch (error) {
|
|
if (error instanceof PentestError) {
|
|
await logError(error, 'Saving run metadata', sourceDir);
|
|
throw error; // Re-throw PentestError to be handled by caller
|
|
}
|
|
|
|
const metadataError = new PentestError(
|
|
`Run metadata saving failed: ${error.message}`,
|
|
'filesystem',
|
|
false,
|
|
{ sourceDir, originalError: error.message }
|
|
);
|
|
await logError(metadataError, 'Saving run metadata', sourceDir);
|
|
throw metadataError;
|
|
}
|
|
} |