algorise's picture
download
raw
17.5 kB
import os
import shutil
import time
import sys
import logging
import stat
import subprocess
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from tools import (
ReadFileTool,
WriteFileTool,
ListDirectoryTool,
GlobTool,
ShellTool,
EditTool,
GrepTool,
ReadManyFilesTool,
BackendTestTool
)
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
template_root = os.path.join(os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "templates")
TEMPLATES = {
"root_dir": template_root,
"templates": [
{
"name": "nextjs-nestjs-postresql",
"install": [
"npm run install:all"
],
"start": [
"npm run dev"
],
"description": "A full-stack template using Next.js for the frontend, NestJS for the backend, and PostgreSQL as the database, suitable for building full-stack web applications with a modern, scalable architecture.",
"common_instruction": """This project template uses Next.js for the frontend, NestJS for the backend, and PostgreSQL as the database.
Quick Start
1. Install dependencies:
```bash
npm run install:all
```
2. Start services:
```bash
# Start frontend and backend
npm run dev
```
3. Access the applications:
- Frontend: http://localhost:3000
- Backend API: http://localhost:3001
Common Issues and Solutions
- Ensure all services are running on their correct ports. If ports are already in use, find and kill processes bound to them using `ss -tulnp | grep :PORT` and `kill -9 [PID]`
- Verify CORS configuration in `backend/src/main.ts` if frontend cannot connect to backend
IMPORTANT: The PostgreSQL database has been initialized automatically. You **must not** modify the database connection settings, or try to use a different database.""",
# frontend instruction
"frontend_instruction": """Frontend (Next.js)
Key directories and files:
- `frontend/src/app`: Create new pages or modify existing ones here. Follow Next.js App Router conventions (e.g., `page.tsx` for pages, `layout.tsx` for layouts).
- `frontend/src/components`: Build reusable UI components in this directory.
- `frontend/src/lib`: Place utilities for interacting with your backend API here.
- Environment Variables: Configure frontend-specific environment variables in `frontend/.env.local`.
Coloring:
- The color of the background and components of ALL the pages must fit the color described in the **user instruction**.
- The color MUST have obvious contrast. For example, a light-colored component must have dark-colored text, and a dark-colored component must have light-colored text. The components and the background color must have similar contrast.
- You should modify the tailwindcss classes and configurations in the codebase.
IMPORTANT:
- When importing tailwind in files such as `globals.css`, ALWAYS use the new Tailwind import syntax `@import "tailwindcss";` at the start of the file.
- Do NOT use the three lines of old Tailwind import syntax `@tailwind base;`, `@tailwind components;`, and `@tailwind utilities;`. They will cause rendering failures and jumbled pages.
- ALL content about the template or the technologies used MUST be removed from the final website.
You'll primarily work within the `frontend/src`.""",
# backend instruction
"backend_instruction": f"""Backend (NestJS)
Project Structure
```
backend/src/
├── app.controller.ts # Main application controller
├── app.module.ts # Root application module
├── app.service.ts # Main application service
└── main.ts # Application entry point
```
Development Workflow:
1. Creating new module directory (`backend/src/<module-name>`)
2. Generate controller and service (`*.controller.ts`, `*.service.ts`)
3. Create entity (if needed; `*.entity.ts`)
Key directories and files:
- `backend/src/<module-name>`: Create new NestJS modules here for your features, each containing controllers, services, and entities.
- Controllers (`*.controller.ts`): Define your REST API endpoints.
- Services (`*.service.ts`): Implement your business logic.
- Entities (`*.entity.ts`): Define your data models and database schema using TypeORM.
- Database Configuration: Adjust database settings in `backend/src/app.module.ts` or via `backend/.env`.
- DTOs (`dto/*.dto.ts`): Define data transfer objects for validation and type safety.
IMPORTANT: Notice that a global prefix `/api/` has been set in `backend/src/main.ts`, so don't add it again in individual endpoints:
```
app.setGlobalPrefix('api');
```
Route-Order CAUTION:
- Always declare fixed/static routes BEFORE parameterised routes.
Example:
@Get('search') // static, goes first
search() {{ ... }}
@Get(':id') // dynamic, goes after the static route
findOne() {{ ... }}
- Symptom if you forget:
- If you place `:id` first, calling “…/search” throws would try to match the string 'search' to `:id`, which will cause an error.
- If you encounter a validation error and the type validations all seem correct, this might be the reason.
Database Configuration:
The application uses TypeORM with PostgreSQL. The database is running on port 5432. Configuration is loaded from environment variables in `backend/.env`:
```
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=myappuser
DB_PASSWORD=myapppassword
DB_NAME=myapp
```
IMPORTANT:
- When testing the implemented background endpoints, NEVER start the service in the shell. NEVER use `curl` to send the testing requests.
- ALWAYS use the specialized backend testing tool, `{BackendTestTool.Name}`, to make the tests.
- Continue testing the APIs using `{BackendTestTool.Name}` until ALL the APIs are correctly functioning!
- Every time a new API endpoint has been implemented, install the dependencies using `{ShellTool.Name}`, then call `{BackendTestTool.Name}` to test it.
- ALWAYS add some mock data if the data returned from the testing is empty.
- You should ALWAYS inject some testing data into the database to make sure that the return is not empty!
Note: You can inject test data into the database by running `sudo -u postgres psql -d myapp -c "[SQL_COMMAND]"` with the shell tool `{ShellTool.Name}`.
Examples:
```
# create table
sudo -u postgres psql -d myapp -c 'CREATE TABLE test_table (id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL);'
# insert data
sudo -u postgres psql -d myapp -c "INSERT INTO test_table (name) VALUES ('Test Row');"
# view table
sudo -u postgres psql -d myapp -c "SELECT * FROM test_table;"
```
You'll primarily work within the `backend/src`."""
}
]
}
def safe_remove_path(path, max_retries=15, delay=1, force=True):
"""Safely remove a file or directory with retries and optional force flag
Args:
path: Path to file or directory to remove
max_retries: Maximum number of retry attempts
delay: Delay between retries in seconds
force: Use more aggressive methods on final attempt
Returns:
bool: True if successful, False otherwise
"""
logger.info(f"Attempting to remove path: {path}")
for attempt in range(max_retries):
try:
if not os.path.exists(path):
logger.info(f"Path does not exist: {path}")
return True
# On final attempt with force flag, use more aggressive methods
if force and attempt >= max_retries - 3: # Start aggressive methods earlier
logger.info(f"Using force removal for {path} (attempt {attempt + 1})")
result = _force_remove(path)
if result:
logger.info(f"Force removal successful for {path}")
return True
elif attempt == max_retries - 1:
logger.error(f"Force removal failed for {path} after {max_retries} attempts")
return False
if os.path.isfile(path) or os.path.islink(path):
os.remove(path)
logger.info(f"Removed file: {path}")
return True
elif os.path.isdir(path):
shutil.rmtree(path)
logger.info(f"Removed directory: {path}")
return True
except (OSError, PermissionError, shutil.Error) as e:
if attempt == max_retries - 1:
if force:
logger.warning(f"Final attempt using force removal for {path}")
return _force_remove(path)
else:
logger.error(f"Failed to remove {path} after {max_retries} attempts: {e}")
raise
logger.warning(f"Attempt {attempt + 1} failed for {path}: {e}. Retrying in {delay}s...")
time.sleep(delay)
return False
def _force_remove(path):
"""Internal function to forcefully remove files/directories"""
logger.info(f"Starting force removal for: {path}")
try:
if not os.path.exists(path):
logger.info(f"Path does not exist: {path}")
return True
# Make everything writable first
if os.path.isdir(path):
logger.info(f"Making directory contents writable: {path}")
for root, dirs, files in os.walk(path):
for item in files + dirs:
item_path = os.path.join(root, item)
try:
os.chmod(item_path, stat.S_IWRITE | stat.S_IREAD)
except Exception as e:
logger.debug(f"Failed to chmod {item_path}: {e}")
os.chmod(path, stat.S_IWRITE | stat.S_IREAD)
else:
logger.info(f"Making file writable: {path}")
os.chmod(path, stat.S_IWRITE | stat.S_IREAD)
# Try standard removal again
if os.path.isfile(path) or os.path.islink(path):
os.remove(path)
logger.info(f"Removed file with chmod: {path}")
return True
elif os.path.isdir(path):
shutil.rmtree(path)
logger.info(f"Removed directory with chmod: {path}")
return True
except Exception as e:
logger.warning(f"Standard force removal failed for {path}: {e}")
# Try killing processes that might be using files in the directory
try:
_kill_processes_using_path(path)
except Exception as kill_error:
logger.warning(f"Failed to kill processes using {path}: {kill_error}")
# Platform-specific fallbacks with more robust options
try:
if os.name == 'nt':
# Use Windows command line for force deletion
logger.info(f"Using Windows cmd to remove: {path}")
if os.path.isdir(path):
subprocess.run(['cmd', '/c', 'rmdir', '/s', '/q', path],
shell=True, check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
else:
subprocess.run(['cmd', '/c', 'del', '/f', '/q', path],
shell=True, check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
else: # Unix-like systems
# Use rm -rf command with additional flags
logger.info(f"Using rm command to remove: {path}")
subprocess.run(['rm', '-rf', '--one-file-system', path],
check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
logger.info(f"Fallback removal successful for: {path}")
return True
except subprocess.CalledProcessError:
# Try one more time with even more force
try:
if os.name != 'nt':
logger.info(f"Using rm with additional flags for: {path}")
subprocess.run(['rm', '-rf', '--one-file-system', '--preserve-root=no', path],
check=True, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
logger.info(f"Extended rm successful for: {path}")
return True
except Exception as unix_error:
logger.error(f"Unix force removal failed for {path}: {unix_error}")
except Exception as fallback_error:
logger.error(f"Fallback removal also failed for {path}: {fallback_error}")
# Final attempt: move then delete
try:
logger.info(f"Trying move-then-delete for: {path}")
temp_name = path + ".deleting_" + str(int(time.time()))
os.rename(path, temp_name)
if os.path.isfile(temp_name) or os.path.islink(temp_name):
os.remove(temp_name)
elif os.path.isdir(temp_name):
shutil.rmtree(temp_name)
logger.info(f"Move-then-delete successful for: {path}")
return True
except Exception as move_error:
logger.error(f"Move-then-delete also failed for {path}: {move_error}")
return False
def _kill_processes_using_path(path):
"""Kill processes that might be using files in the path"""
if os.name != 'nt': # Unix-like systems
try:
logger.info(f"Checking for processes using: {path}")
# Find processes using files in the directory
result = subprocess.run(['lsof', '+D', path],
capture_output=True, text=True)
if result.returncode == 0 and result.stdout:
lines = result.stdout.strip().split('\n')[1:] # Skip header
pids = set()
for line in lines:
parts = line.split()
if len(parts) > 1:
pids.add(parts[1])
# Kill the processes
for pid in pids:
try:
logger.info(f"Killing process {pid} using {path}")
subprocess.run(['kill', '-9', pid],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except Exception as e:
logger.warning(f"Failed to kill process {pid}: {e}")
except Exception as e:
logger.warning(f"Failed to check/processes using {path}: {e}")
def safe_copy_template(template_source, working_dir, max_retries=5):
"""Safely copy template with error handling"""
for attempt in range(max_retries):
try:
# Clear working directory contents
if os.path.exists(working_dir):
for item in os.listdir(working_dir):
item_path = os.path.join(working_dir, item)
safe_remove_path(item_path)
# Copy template contents
for item in os.listdir(template_source):
source_item = os.path.join(template_source, item)
dest_item = os.path.join(working_dir, item)
if os.path.isfile(source_item):
shutil.copy2(source_item, dest_item)
elif os.path.isdir(source_item):
shutil.copytree(source_item, dest_item)
# Run install commands if they exist
subprocess.run("npm -v", shell=True, check=True, cwd=working_dir)
subprocess.run("node --version", shell=True, check=True, cwd=working_dir)
subprocess.run("npm config set registry https://registry.npmmirror.com", shell=True, check=True, cwd=working_dir)
template_config = _get_template_config(template_source)
if template_config and "install" in template_config:
for command in template_config["install"]:
try:
logger.info(f"Running install command: {command}")
subprocess.run(command, shell=True, check=True, cwd=working_dir)
except subprocess.CalledProcessError as e:
logger.error(f"Install command failed: {e}")
raise
return True
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"Copy attempt {attempt + 1} failed: {e}. Retrying...")
time.sleep(1)
return False
def _get_template_config(template_source):
"""Get template configuration from TEMPLATES dict based on template source"""
# Extract template name from path (last directory name)
template_name = os.path.basename(template_source)
# Find matching template in TEMPLATES config
for template in TEMPLATES.get("templates", []):
if template.get("name") == template_name:
return template
logger.warning(f"No template config found for {template_name}")
return None
if __name__ == "__main__":
safe_remove_path("/mnt/cache/agent/Zimu/WebGen-Agent2/workspaces_root/test_project17")

Xet Storage Details

Size:
17.5 kB
·
Xet hash:
db00e42d168cd3219670dcc30adca658ecf536d5a0772ed1c9454438d2fa5226

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.