diff --git a/apps/backend/scripts/clean.ts b/apps/backend/scripts/clean.ts index 7e8a815..9e32209 100644 --- a/apps/backend/scripts/clean.ts +++ b/apps/backend/scripts/clean.ts @@ -1,13 +1,19 @@ -import { fileManager } from '../src/utils/fileManager'; +import { cleanupProject } from '../src/utils/fileManager'; async function main() { const customPath = process.argv[2]; + if (!customPath) { + console.error('Please provide a path to clean'); + console.log('Usage: bun run clean-advanced -- /path/to/project'); + process.exit(1); + } + try { - await fileManager.cleanupProject(customPath); - console.log(' Cleanup completed successfully'); + await cleanupProject(customPath); + console.log('Advanced cleanup completed successfully'); } catch (error) { - console.error(' Cleanup failed:', error instanceof Error ? error.message : error); + console.error('Advanced cleanup failed:', error instanceof Error ? error.message : error); process.exit(1); } } diff --git a/apps/backend/scripts/setup.ts b/apps/backend/scripts/setup.ts index ed6b364..9b6e1f0 100644 --- a/apps/backend/scripts/setup.ts +++ b/apps/backend/scripts/setup.ts @@ -1,15 +1,51 @@ -import { fileManager } from '../src/utils/fileManager'; +import { setupProject } from '../src/utils/fileManager'; +import { join } from 'node:path'; +import { promises as fs } from 'node:fs'; async function main() { try { - const projectPath = await fileManager.setupProject(); - console.log('Project created at:', projectPath); - console.log('\nTo clean up later, run:'); - console.log('bun run clean'); - console.log('or to clean a specific path:'); - console.log('bun run clean -- /path/to/project'); + // Test with custom options + const project = await setupProject({ + baseName: 'my-advanced-contract', + rustCode: `#![no_std] +use soroban_sdk::{contractimpl, Env, log}; + +pub struct Contract; + +#[contractimpl] +impl Contract { + pub fn sum(env: Env, a: i32, b: i32) -> i32 { + log!(&env, "Adding {} and {}", a, b); + a + b + } + + pub fn multiply(env: Env, a: i32, b: i32) -> i32 { + a * b + } +}` + }); + + console.log('Advanced project created at:', project.tempDir); + + // Verify files were created + const libRsPath = join(project.tempDir, 'src', 'lib.rs'); + const cargoTomlPath = join(project.tempDir, 'Cargo.toml'); + + console.log('\nFiles created:'); + console.log('-', libRsPath); + console.log('-', cargoTomlPath); + + console.log('\nlib.rs content:'); + console.log(await fs.readFile(libRsPath, 'utf8')); + + console.log('\nProject will be automatically cleaned up in 30 seconds...'); + await new Promise(resolve => setTimeout(resolve, 30000)); + + // Cleanup will happen automatically when the cleanup function is called + await project.cleanup(); + console.log('\nCleanup completed successfully'); } catch (error) { - console.error('Setup failed:', error instanceof Error ? error.message : error); + console.error('Advanced setup failed:', error instanceof Error ? error.message : error); process.exit(1); } } diff --git a/apps/backend/src/utils/fileManager.ts b/apps/backend/src/utils/fileManager.ts index 45d427b..d356b22 100644 --- a/apps/backend/src/utils/fileManager.ts +++ b/apps/backend/src/utils/fileManager.ts @@ -22,20 +22,63 @@ export interface ProjectSetupOptions { baseName?: string; /** Custom temporary directory root (defaults to OS temp dir) */ tempRoot?: string; + /** Custom Rust code for the contract */ + rustCode?: string; } +/** + * Default Rust contract template + * This is a simple Soroban contract that greets a user by name. + * It can be customized by passing a different `rustCode` in the options. + */ +const DEFAULT_RUST_CODE = `#![no_std] +use soroban_sdk::{contractimpl, Env}; + +pub struct Contract; + +#[contractimpl] +impl Contract { + pub fn hello(env: Env, name: String) -> String { + format!("Hello, {}!", name) + } +}`; + +/** + * Default Cargo.toml template for Soroban contracts + */ +const DEFAULT_CARGO_TOML = `[package] +name = "temp-contract" +version = "0.1.0" +edition = "2021" + +[lib] +name = "temp_project" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +soroban-sdk = "21.2.0" + +[dev-dependencies] +soroban-sdk = { version = "21.2.0", features = ["testutils"] } + +[profile.release] +opt-level = "z" +overflow-checks = true +debug = 0 +strip = "symbols" +debug-assertions = false +panic = "abort" +codegen-units = 1 +lto = true + +[profile.release-with-logs] +inherits = "release" +debug-assertions = true +`; + /** * Sanitizes a directory name to prevent path traversal and ensure cross-platform compatibility - * - * @param baseName - The base name to sanitize - * @returns A sanitized directory name safe for use across platforms - * - * @example - * ```typescript - * getSanitizedDirName('../malicious') // returns 'malicious' - * getSanitizedDirName('CON') // returns '' (Windows reserved name) - * getSanitizedDirName('my-project') // returns 'my-project' - * ``` */ export function getSanitizedDirName(baseName: string): string { if (!baseName || typeof baseName !== 'string') { @@ -43,52 +86,25 @@ export function getSanitizedDirName(baseName: string): string { } const trimmed = baseName.trim(); - - // Handle whitespace-only strings if (!trimmed) { return ''; } - // Sanitize the filename to remove dangerous characters and reserved names let sanitized = sanitizeFilename(trimmed, { replacement: '_' }); - - // Additional cleanup for path traversal attempts sanitized = sanitized.replace(/\.\./g, '').replace(/^[._]+/, ''); - // Ensure it's not too long (filesystem limit is usually 255, leave room for timestamp/random) if (sanitized.length > 50) { sanitized = sanitized.substring(0, 50); } - // Additional safety: ensure it's not empty after sanitization - if (!sanitized || sanitized.length === 0) { - return 'project'; - } - - return sanitized; + return sanitized || 'project'; } /** * Creates a unique, sanitized temporary directory for Rust project compilation - * - * @param options - Configuration options for directory creation - * @returns Promise resolving to ProjectSetup with directory path and cleanup function - * - * @throws {Error} When directory creation fails - * - * @example - * ```typescript - * const project = await setupProject({ baseName: 'my-contract' }); - * try { - * // Use project.tempDir for compilation - * console.log('Working in:', project.tempDir); - * } finally { - * await project.cleanup(); - * } - * ``` */ export async function setupProject(options: ProjectSetupOptions = {}): Promise { - const { baseName = 'project', tempRoot = tmpdir() } = options; + const { baseName = 'project', tempRoot = tmpdir(), rustCode = DEFAULT_RUST_CODE } = options; // Create a unique identifier to prevent collisions const timestamp = Date.now(); @@ -96,8 +112,6 @@ export async function setupProject(options: ProjectSetupOptions = {}): Promise

cleanupProject(tempDir), @@ -125,14 +142,6 @@ export async function setupProject(options: ProjectSetupOptions = {}): Promise

{ if (!tempDir || typeof tempDir !== 'string') { @@ -149,7 +158,6 @@ export async function cleanupProject(tempDir: string): Promise { // Check if directory exists before attempting to remove const stats = await fs.stat(tempDir).catch(() => null); if (!stats) { - // Directory doesn't exist, nothing to clean return; } @@ -157,7 +165,6 @@ export async function cleanupProject(tempDir: string): Promise { throw new Error(`Path is not a directory: ${tempDir}`); } - // Remove the directory and all its contents await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { throw new Error( @@ -168,10 +175,6 @@ export async function cleanupProject(tempDir: string): Promise { /** * Creates a basic Rust project structure with Cargo.toml and lib.rs - * - * @param tempDir - The temporary directory to create the project in - * @param rustCode - The Rust code to write to lib.rs - * @throws {Error} When file creation fails */ export async function createRustProject(tempDir: string, rustCode: string): Promise { if (!tempDir || typeof tempDir !== 'string') { @@ -184,41 +187,13 @@ export async function createRustProject(tempDir: string, rustCode: string): Prom try { // Create Cargo.toml for Soroban contract - const cargoToml = `[package] -name = "temp-contract" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -soroban-sdk = "21" - -[dev-dependencies] -soroban-sdk = { version = "21", features = ["testutils"] } - -[profile.release] -opt-level = "z" -overflow-checks = true -debug = 0 -strip = "symbols" -debug-assertions = false -panic = "abort" -codegen-units = 1 -lto = true - -[profile.release-with-logs] -inherits = "release" -debug-assertions = true -`; // Create src directory const srcDir = join(tempDir, 'src'); await fs.mkdir(srcDir, { recursive: true }); // Write Cargo.toml - await fs.writeFile(join(tempDir, 'Cargo.toml'), cargoToml, 'utf8'); + await fs.writeFile(join(tempDir, 'Cargo.toml'), DEFAULT_CARGO_TOML, 'utf8'); // Write lib.rs await fs.writeFile(join(srcDir, 'lib.rs'), rustCode, 'utf8');