Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion file_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import shutil
import stat
from pathlib import Path

from liquid2 import Environment, FileSystemLoader, StrictUndefined
Expand Down Expand Up @@ -94,7 +95,22 @@ def list_folders_in_directory(directory):
# delete a folder and all its subfolders and files
def delete_folder(folder_name):
if os.path.exists(folder_name):
shutil.rmtree(folder_name)
# Use writable-aware deletion so read-only files (e.g. .git objects on Windows) don't cause PermissionError
delete_files_and_subfolders(folder_name)
_make_writable(folder_name)
os.rmdir(folder_name)


def _make_writable(path: str) -> None:
"""On Windows, clear read-only so deletion can succeed."""
# TODO - Check if this can be done in a cleaner way.
is_windows = os.name == "nt"
if is_windows:
try:
mode = os.stat(path).st_mode
os.chmod(path, mode | stat.S_IWRITE)
except OSError:
pass


def delete_files_and_subfolders(directory):
Expand All @@ -106,12 +122,14 @@ def delete_files_and_subfolders(directory):
# Delete files
for file in files:
file_path = os.path.join(root, file)
_make_writable(file_path)
os.remove(file_path)
total_files_deleted += 1

# Delete directories
for dir_ in dirs:
dir_path = os.path.join(root, dir_)
_make_writable(dir_path)
os.rmdir(dir_path)
total_folders_deleted += 1

Expand Down
2 changes: 1 addition & 1 deletion install/powershell/examples.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$ErrorActionPreference = 'Stop'
$ErrorActionPreference = 'Stop'

# Brand Colors (use exported colors if available, otherwise define them)
if (-not $env:YELLOW) { $YELLOW = "$([char]27)[38;2;224;255;110m" } else { $YELLOW = $env:YELLOW }
Expand Down
37 changes: 35 additions & 2 deletions install/powershell/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,29 @@ if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
Write-Host "${GREEN}✓${NC} uv detected"
Write-Host ""

try {
$uvOutput = uv tool list 2>$null
} catch {
$uvOutput = @()
}

# Install or upgrade codeplain using uv tool
$codeplainLine = @(uv tool list 2>$null) | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1
$codeplainLine = $uvOutput | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1
if ($codeplainLine) {
$currentVersion = ($codeplainLine -replace 'codeplain v', '').Trim()
Write-Host "${GRAY}codeplain ${currentVersion} is already installed.${NC}"
Write-Host "upgrading to latest version..."
Write-Host ""
uv tool upgrade codeplain 2>&1 | Out-Null

try {
$upgradeOutput = & uv tool upgrade codeplain 2>&1
if ($LASTEXITCODE -ne 0) {
throw "uv exited with code $LASTEXITCODE"
}
} catch {
Write-Host "${RED}Failed to upgrade codeplain.${NC}"
Write-Host $_
}
$newLine = @(uv tool list 2>$null) | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1
$newVersion = ($newLine -replace 'codeplain v', '').Trim()
if ($currentVersion -eq $newVersion) {
Expand All @@ -83,6 +98,24 @@ if ($codeplainLine) {
Write-Host "${GREEN}✓ codeplain installed successfully!${NC}"
}

# Ensure uv tool bin directory is on user PATH permanently (so codeplain is available)
$uvBinDir = Join-Path $env:USERPROFILE '.local\bin'
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath) {
$pathEntries = $userPath -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
if ($uvBinDir -notin $pathEntries) {
$newPath = ($userPath.TrimEnd(';') + ';' + $uvBinDir)
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
$env:Path = $uvBinDir + ';' + $env:Path
Write-Host "${GREEN}✓${NC} added $uvBinDir to your user PATH"
}
} else {
[Environment]::SetEnvironmentVariable('Path', $uvBinDir, 'User')
$env:Path = $uvBinDir + ';' + $env:Path
Write-Host "${GREEN}✓${NC} added $uvBinDir to your user PATH"
}
Write-Host ""

# Check if API key already exists
$skipApiKeySetup = $false
if ($env:CODEPLAIN_API_KEY) {
Expand Down
2 changes: 1 addition & 1 deletion install/powershell/walkthrough.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
$ErrorActionPreference = 'Stop'
$ErrorActionPreference = 'Stop'

# Brand Colors (use exported colors if available, otherwise define them)
if (-not $env:YELLOW) { $YELLOW = "$([char]27)[38;2;224;255;110m" } else { $YELLOW = $env:YELLOW }
Expand Down
20 changes: 17 additions & 3 deletions render_machine/render_utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import subprocess
import sys
import tempfile
import time
from typing import Optional
Expand Down Expand Up @@ -51,19 +52,30 @@ def execute_script(
) -> tuple[int, str, Optional[str]]:
temp_file_path = None
script_timeout = timeout if timeout is not None else SCRIPT_EXECUTION_TIMEOUT

script_path = file_utils.add_current_path_if_no_path(script)
# On Windows, .ps1 files must be run via PowerShell, not as the executable
if sys.platform == "win32" and script_path.lower().endswith(".ps1"):
cmd = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script_path] + scripts_args
else:
cmd = [script_path] + scripts_args
try:
start_time = time.time()
result = subprocess.run(
[file_utils.add_current_path_if_no_path(script)] + scripts_args,
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
encoding="utf-8",
errors="replace",
timeout=script_timeout,
)
elapsed_time = time.time() - start_time
# Log the info about the script execution
if verbose:
with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".script_output") as temp_file:
with tempfile.NamedTemporaryFile(
mode="w+", encoding="utf-8", delete=False, suffix=".script_output"
) as temp_file:
temp_file.write(f"\n═════════════════════════ {script_type} Script Output ═════════════════════════\n")
temp_file.write(result.stdout)
temp_file.write("\n══════════════════════════════════════════════════════════════════════\n")
Expand Down Expand Up @@ -95,7 +107,9 @@ def execute_script(
except subprocess.TimeoutExpired as e:
# Store timeout output in a temporary file
if verbose:
with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".script_timeout") as temp_file:
with tempfile.NamedTemporaryFile(
mode="w+", encoding="utf-8", delete=False, suffix=".script_timeout"
) as temp_file:
temp_file.write(f"{script_type} script {script} timed out after {script_timeout} seconds.")
if e.stdout:
decoded_output = e.stdout.decode("utf-8") if isinstance(e.stdout, bytes) else e.stdout
Expand Down