Skip to content

Commit 0ea424b

Browse files
committed
Fixed installation scripts with correct encoding, added test script running for ps1 files. Adjusted file_utils to be compatible with windows.
1 parent 7a3a0f3 commit 0ea424b

File tree

5 files changed

+73
-8
lines changed

5 files changed

+73
-8
lines changed

file_utils.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import shutil
3+
import stat
34
from pathlib import Path
45

56
from liquid2 import Environment, FileSystemLoader, StrictUndefined
@@ -94,7 +95,22 @@ def list_folders_in_directory(directory):
9495
# delete a folder and all its subfolders and files
9596
def delete_folder(folder_name):
9697
if os.path.exists(folder_name):
97-
shutil.rmtree(folder_name)
98+
# Use writable-aware deletion so read-only files (e.g. .git objects on Windows) don't cause PermissionError
99+
delete_files_and_subfolders(folder_name)
100+
_make_writable(folder_name)
101+
os.rmdir(folder_name)
102+
103+
104+
def _make_writable(path: str) -> None:
105+
"""On Windows, clear read-only so deletion can succeed."""
106+
# TODO - Check if this can be done in a cleaner way.
107+
is_windows = os.name == "nt"
108+
if is_windows:
109+
try:
110+
mode = os.stat(path).st_mode
111+
os.chmod(path, mode | stat.S_IWRITE)
112+
except OSError:
113+
pass
98114

99115

100116
def delete_files_and_subfolders(directory):
@@ -106,12 +122,14 @@ def delete_files_and_subfolders(directory):
106122
# Delete files
107123
for file in files:
108124
file_path = os.path.join(root, file)
125+
_make_writable(file_path)
109126
os.remove(file_path)
110127
total_files_deleted += 1
111128

112129
# Delete directories
113130
for dir_ in dirs:
114131
dir_path = os.path.join(root, dir_)
132+
_make_writable(dir_path)
115133
os.rmdir(dir_path)
116134
total_folders_deleted += 1
117135

install/powershell/examples.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
$ErrorActionPreference = 'Stop'
1+
$ErrorActionPreference = 'Stop'
22

33
# Brand Colors (use exported colors if available, otherwise define them)
44
if (-not $env:YELLOW) { $YELLOW = "$([char]27)[38;2;224;255;110m" } else { $YELLOW = $env:YELLOW }

install/powershell/install.ps1

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,29 @@ if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
6060
Write-Host "${GREEN}${NC} uv detected"
6161
Write-Host ""
6262

63+
try {
64+
$uvOutput = uv tool list 2>$null
65+
} catch {
66+
$uvOutput = @()
67+
}
68+
6369
# Install or upgrade codeplain using uv tool
64-
$codeplainLine = @(uv tool list 2>$null) | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1
70+
$codeplainLine = $uvOutput | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1
6571
if ($codeplainLine) {
6672
$currentVersion = ($codeplainLine -replace 'codeplain v', '').Trim()
6773
Write-Host "${GRAY}codeplain ${currentVersion} is already installed.${NC}"
6874
Write-Host "upgrading to latest version..."
6975
Write-Host ""
70-
uv tool upgrade codeplain 2>&1 | Out-Null
76+
77+
try {
78+
$upgradeOutput = & uv tool upgrade codeplain 2>&1
79+
if ($LASTEXITCODE -ne 0) {
80+
throw "uv exited with code $LASTEXITCODE"
81+
}
82+
} catch {
83+
Write-Host "${RED}Failed to upgrade codeplain.${NC}"
84+
Write-Host $_
85+
}
7186
$newLine = @(uv tool list 2>$null) | Where-Object { $_ -match '^codeplain' } | Select-Object -First 1
7287
$newVersion = ($newLine -replace 'codeplain v', '').Trim()
7388
if ($currentVersion -eq $newVersion) {
@@ -83,6 +98,24 @@ if ($codeplainLine) {
8398
Write-Host "${GREEN}✓ codeplain installed successfully!${NC}"
8499
}
85100

101+
# Ensure uv tool bin directory is on user PATH permanently (so codeplain is available)
102+
$uvBinDir = Join-Path $env:USERPROFILE '.local\bin'
103+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
104+
if ($userPath) {
105+
$pathEntries = $userPath -split ';' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
106+
if ($uvBinDir -notin $pathEntries) {
107+
$newPath = ($userPath.TrimEnd(';') + ';' + $uvBinDir)
108+
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
109+
$env:Path = $uvBinDir + ';' + $env:Path
110+
Write-Host "${GREEN}${NC} added $uvBinDir to your user PATH"
111+
}
112+
} else {
113+
[Environment]::SetEnvironmentVariable('Path', $uvBinDir, 'User')
114+
$env:Path = $uvBinDir + ';' + $env:Path
115+
Write-Host "${GREEN}${NC} added $uvBinDir to your user PATH"
116+
}
117+
Write-Host ""
118+
86119
# Check if API key already exists
87120
$skipApiKeySetup = $false
88121
if ($env:CODEPLAIN_API_KEY) {

install/powershell/walkthrough.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
$ErrorActionPreference = 'Stop'
1+
$ErrorActionPreference = 'Stop'
22

33
# Brand Colors (use exported colors if available, otherwise define them)
44
if (-not $env:YELLOW) { $YELLOW = "$([char]27)[38;2;224;255;110m" } else { $YELLOW = $env:YELLOW }

render_machine/render_utils.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import subprocess
2+
import sys
23
import tempfile
34
import time
45
from typing import Optional
@@ -51,19 +52,30 @@ def execute_script(
5152
) -> tuple[int, str, Optional[str]]:
5253
temp_file_path = None
5354
script_timeout = timeout if timeout is not None else SCRIPT_EXECUTION_TIMEOUT
55+
56+
script_path = file_utils.add_current_path_if_no_path(script)
57+
# On Windows, .ps1 files must be run via PowerShell, not as the executable
58+
if sys.platform == "win32" and script_path.lower().endswith(".ps1"):
59+
cmd = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script_path] + scripts_args
60+
else:
61+
cmd = [script_path] + scripts_args
5462
try:
5563
start_time = time.time()
5664
result = subprocess.run(
57-
[file_utils.add_current_path_if_no_path(script)] + scripts_args,
65+
cmd,
5866
stdout=subprocess.PIPE,
5967
stderr=subprocess.STDOUT,
6068
text=True,
69+
encoding="utf-8",
70+
errors="replace",
6171
timeout=script_timeout,
6272
)
6373
elapsed_time = time.time() - start_time
6474
# Log the info about the script execution
6575
if verbose:
66-
with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".script_output") as temp_file:
76+
with tempfile.NamedTemporaryFile(
77+
mode="w+", encoding="utf-8", delete=False, suffix=".script_output"
78+
) as temp_file:
6779
temp_file.write(f"\n═════════════════════════ {script_type} Script Output ═════════════════════════\n")
6880
temp_file.write(result.stdout)
6981
temp_file.write("\n══════════════════════════════════════════════════════════════════════\n")
@@ -95,7 +107,9 @@ def execute_script(
95107
except subprocess.TimeoutExpired as e:
96108
# Store timeout output in a temporary file
97109
if verbose:
98-
with tempfile.NamedTemporaryFile(mode="w+", delete=False, suffix=".script_timeout") as temp_file:
110+
with tempfile.NamedTemporaryFile(
111+
mode="w+", encoding="utf-8", delete=False, suffix=".script_timeout"
112+
) as temp_file:
99113
temp_file.write(f"{script_type} script {script} timed out after {script_timeout} seconds.")
100114
if e.stdout:
101115
decoded_output = e.stdout.decode("utf-8") if isinstance(e.stdout, bytes) else e.stdout

0 commit comments

Comments
 (0)