2025-12-23 16:36:39 -08:00
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Bootstrap a Python virtualenv and install the project on Windows (PowerShell).
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Creates a Python virtual environment (default: .venv), upgrades pip, installs the project
|
|
|
|
|
(either editable or normal), and optionally creates Desktop and Start Menu shortcuts.
|
|
|
|
|
|
|
|
|
|
.EXAMPLE
|
|
|
|
|
# Create .venv and install in editable mode, create Desktop shortcut
|
|
|
|
|
.\scripts\bootstrap.ps1 -Editable -CreateDesktopShortcut
|
|
|
|
|
|
|
|
|
|
.EXAMPLE
|
|
|
|
|
# Use a specific python executable and force overwrite existing venv
|
|
|
|
|
.\scripts\bootstrap.ps1 -Python "C:\\Python39\\python.exe" -Force
|
|
|
|
|
# Note: you may need to run PowerShell with ExecutionPolicy Bypass:
|
|
|
|
|
# powershell -ExecutionPolicy Bypass -File .\scripts\bootstrap.ps1 -Editable
|
|
|
|
|
#>
|
|
|
|
|
|
|
|
|
|
param(
|
|
|
|
|
[switch]$Editable,
|
|
|
|
|
[switch]$CreateDesktopShortcut,
|
|
|
|
|
[switch]$CreateStartMenuShortcut,
|
|
|
|
|
[string]$VenvPath = ".venv",
|
|
|
|
|
[string]$Python = "",
|
|
|
|
|
[switch]$Force,
|
|
|
|
|
[switch]$NoInstall,
|
2025-12-24 02:13:21 -08:00
|
|
|
[switch]$NoPlaywright,
|
|
|
|
|
[string]$PlaywrightBrowsers = "chromium",
|
2025-12-24 02:32:15 -08:00
|
|
|
[switch]$FixUrllib3,
|
2025-12-24 02:42:13 -08:00
|
|
|
[switch]$RemovePth,
|
2025-12-23 16:36:39 -08:00
|
|
|
[switch]$Quiet
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-24 02:59:42 -08:00
|
|
|
# Track whether the user chose an interactive auto-fix (so we can auto-remove .pth files)
|
|
|
|
|
$AutoFixInteractive = $false
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-23 16:36:39 -08:00
|
|
|
function Write-Log {
|
|
|
|
|
param([string]$msg,[string]$lvl="INFO")
|
|
|
|
|
if (-not $Quiet) {
|
|
|
|
|
if ($lvl -eq "ERROR") { Write-Host "[$lvl] $msg" -ForegroundColor Red } else { Write-Host "[$lvl] $msg" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Find-Python {
|
|
|
|
|
param([string]$preferred)
|
|
|
|
|
$candidates = @()
|
|
|
|
|
if ($preferred -and $preferred.Trim()) { $candidates += $preferred }
|
|
|
|
|
$candidates += @("python","python3","py")
|
|
|
|
|
foreach ($c in $candidates) {
|
|
|
|
|
try {
|
|
|
|
|
if ($c -eq "py") {
|
|
|
|
|
$out = & py -3 -c "import sys, json; print(sys.executable)" 2>$null
|
|
|
|
|
if ($out) { return $out.Trim() }
|
|
|
|
|
} else {
|
|
|
|
|
$out = & $c -c "import sys, json; print(sys.executable)" 2>$null
|
|
|
|
|
if ($out) { return $out.Trim() }
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
|
|
|
|
}
|
|
|
|
|
return $null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Resolve OS detection in a broad-compatible way
|
|
|
|
|
try { $IsWindowsPlatform = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) } catch { $IsWindowsPlatform = $env:OS -match 'Windows' }
|
|
|
|
|
|
|
|
|
|
# operate from repo root (parent of scripts dir)
|
|
|
|
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
|
|
|
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
|
|
|
|
Set-Location $repoRoot
|
|
|
|
|
|
|
|
|
|
$pythonExe = Find-Python -preferred $Python
|
|
|
|
|
if (-not $pythonExe) { Write-Log "No Python interpreter found. Specify -Python <path> or install Python." "ERROR"; exit 2 }
|
|
|
|
|
Write-Log "Using Python: $pythonExe"
|
|
|
|
|
|
|
|
|
|
# Full venv path
|
|
|
|
|
try { $venvFull = (Resolve-Path -LiteralPath $VenvPath -ErrorAction SilentlyContinue).Path } catch { $venvFull = $null }
|
|
|
|
|
if (-not $venvFull) { $venvFull = (Join-Path $repoRoot $VenvPath) }
|
|
|
|
|
|
|
|
|
|
# Handle existing venv
|
|
|
|
|
$venvExists = Test-Path $venvFull
|
|
|
|
|
if ($venvExists) {
|
|
|
|
|
if ($Force) {
|
|
|
|
|
Write-Log "Removing existing venv at $venvFull"
|
|
|
|
|
Remove-Item -Recurse -Force $venvFull
|
|
|
|
|
$venvExists = $false
|
|
|
|
|
} else {
|
|
|
|
|
# Quick health check: does the existing venv have a python executable?
|
|
|
|
|
$venvPy1 = Join-Path $venvFull "Scripts\python.exe"
|
|
|
|
|
$venvPy2 = Join-Path $venvFull "bin/python"
|
|
|
|
|
$venvHasPython = $false
|
|
|
|
|
try {
|
|
|
|
|
if (Test-Path $venvPy1 -PathType Leaf -ErrorAction SilentlyContinue) { $venvHasPython = $true }
|
|
|
|
|
elseif (Test-Path $venvPy2 -PathType Leaf -ErrorAction SilentlyContinue) { $venvHasPython = $true }
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
if (-not $venvHasPython) {
|
|
|
|
|
if ($Quiet) {
|
|
|
|
|
Write-Log "Existing venv appears incomplete or broken and quiet mode prevents prompting. Use -Force to recreate." "ERROR"
|
|
|
|
|
exit 4
|
|
|
|
|
}
|
|
|
|
|
$ans = Read-Host "$venvFull exists but appears invalid (no python executable). Overwrite to recreate? (y/N)"
|
|
|
|
|
if ($ans -eq 'y' -or $ans -eq 'Y') {
|
|
|
|
|
Write-Log "Removing broken venv at $venvFull"
|
|
|
|
|
Remove-Item -Recurse -Force $venvFull
|
|
|
|
|
$venvExists = $false
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Aborted due to broken venv." "ERROR"; exit 4
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if ($Quiet) {
|
|
|
|
|
Write-Log "Using existing venv at $venvFull (quiet mode)" "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
$ans = Read-Host "$venvFull already exists. Overwrite? (y/N) (default: use existing venv)"
|
|
|
|
|
if ($ans -eq 'y' -or $ans -eq 'Y') {
|
|
|
|
|
Write-Log "Removing existing venv at $venvFull"
|
|
|
|
|
Remove-Item -Recurse -Force $venvFull
|
|
|
|
|
$venvExists = $false
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Continuing using existing venv at $venvFull" "INFO"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not (Test-Path $venvFull)) {
|
|
|
|
|
Write-Log "Creating venv at $venvFull"
|
|
|
|
|
try {
|
|
|
|
|
& $pythonExe -m venv $venvFull
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Failed to create venv: $_" "ERROR"; exit 3
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Using existing venv at $venvFull" "INFO"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Determine venv python executable
|
|
|
|
|
$venvPython = Join-Path $venvFull "Scripts\python.exe"
|
|
|
|
|
if (-not (Test-Path $venvPython)) { $venvPython = Join-Path $venvFull "bin/python" }
|
|
|
|
|
if (-not (Test-Path $venvPython)) { Write-Log "Created venv but could not find python inside it." "ERROR"; exit 4 }
|
|
|
|
|
|
|
|
|
|
Write-Log "Using venv python: $venvPython"
|
|
|
|
|
|
|
|
|
|
if (-not $NoInstall) {
|
2025-12-24 03:29:45 -08:00
|
|
|
# Suggest editable install for development checkouts (interactive git clones)
|
|
|
|
|
if (-not $Editable) {
|
|
|
|
|
$isGit = $false
|
|
|
|
|
try {
|
|
|
|
|
if (Test-Path (Join-Path $repoRoot '.git')) { $isGit = $true }
|
|
|
|
|
elseif ((Get-Command git -ErrorAction SilentlyContinue) -ne $null) {
|
|
|
|
|
try { $gitOut = & git -C $repoRoot rev-parse --is-inside-work-tree 2>$null; if ($gitOut -eq 'true') { $isGit = $true } } catch {}
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
2025-12-24 03:37:22 -08:00
|
|
|
if ($isGit) {
|
|
|
|
|
$Editable = $true
|
|
|
|
|
Write-Log "Detected development checkout; installing in editable mode for development." "INFO"
|
2025-12-24 03:29:45 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 16:36:39 -08:00
|
|
|
Write-Log "Upgrading pip, setuptools, wheel"
|
|
|
|
|
try { & $venvPython -m pip install -U pip setuptools wheel } catch { Write-Log "pip upgrade failed: $_" "ERROR"; exit 5 }
|
|
|
|
|
|
|
|
|
|
if ($Editable) { $editable_label = "(editable)" } else { $editable_label = "" }
|
|
|
|
|
Write-Log ("Installing project {0}" -f $editable_label)
|
|
|
|
|
try {
|
|
|
|
|
if ($Editable) { & $venvPython -m pip install -e . } else { & $venvPython -m pip install . }
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "pip install failed: $_" "ERROR"; exit 6
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 04:05:35 -08:00
|
|
|
# Verify top-level 'CLI' import and (if missing) attempt to make it available
|
|
|
|
|
Write-Log "Verifying installed CLI import..."
|
|
|
|
|
try {
|
|
|
|
|
& $venvPython -c "import importlib; importlib.import_module('medeia_macina.cli_entry')" 2>$null
|
|
|
|
|
if ($LASTEXITCODE -eq 0) { Write-Log "OK: 'medeia_macina.cli_entry' is importable in the venv." }
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
& $venvPython -c "import importlib; importlib.import_module('CLI')" 2>$null
|
|
|
|
|
if ($LASTEXITCODE -eq 0) { Write-Log "Top-level 'CLI' is importable in the venv." }
|
|
|
|
|
else {
|
|
|
|
|
Write-Log "Top-level 'CLI' not importable; attempting to add repo root to venv site-packages via .pth" "INFO"
|
|
|
|
|
$sites = Get-SitePackages -python $venvPython
|
|
|
|
|
$siteDir = $sites | Where-Object { Test-Path $_ } | Select-Object -First 1
|
|
|
|
|
if ($siteDir) {
|
|
|
|
|
$pth = Join-Path $siteDir 'medeia_repo.pth'
|
|
|
|
|
if (Test-Path $pth) {
|
|
|
|
|
if (-not (Select-String -Path $pth -Pattern ([regex]::Escape($repoRoot)) -Quiet)) {
|
|
|
|
|
Add-Content -Path $pth -Value $repoRoot
|
|
|
|
|
Write-Log "Appended repo root to existing .pth: $pth" "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log ".pth already contains repo root: $pth" "INFO"
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Set-Content -LiteralPath $pth -Value $repoRoot -Encoding UTF8
|
|
|
|
|
Write-Log "Wrote .pth adding repo root to venv site-packages: $pth" "INFO"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Re-check import
|
|
|
|
|
& $venvPython -c "import importlib; importlib.import_module('CLI')" 2>$null
|
|
|
|
|
if ($LASTEXITCODE -eq 0) {
|
|
|
|
|
Write-Log "Top-level 'CLI' import works after adding .pth" "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Adding .pth did not make top-level 'CLI' importable." "ERROR"
|
|
|
|
|
if ($Editable) {
|
|
|
|
|
Write-Log "Editable install already requested; attempting editable reinstall for good measure..." "INFO"
|
|
|
|
|
try { & $venvPython -m pip install -e . } catch { Write-Log "Editable reinstall failed: $_" "ERROR"; exit 6 }
|
|
|
|
|
& $venvPython -c "import importlib; importlib.import_module('CLI')" 2>$null
|
|
|
|
|
if ($LASTEXITCODE -eq 0) { Write-Log "Top-level 'CLI' is now importable after reinstall." "INFO" }
|
|
|
|
|
else { Write-Log "Editable reinstall did not make 'CLI' importable; inspect the venv or create an egg-link manually." "ERROR"; exit 6 }
|
|
|
|
|
} else {
|
|
|
|
|
if (-not $Quiet) {
|
|
|
|
|
$ans = Read-Host "Top-level 'CLI' not importable; install project in editable mode now? (Y/n)"
|
|
|
|
|
if ($ans -eq 'y' -or $ans -eq 'Y') { try { & $venvPython -m pip install -e . } catch { Write-Log "Editable install failed: $_" "ERROR"; exit 6 } }
|
|
|
|
|
else { Write-Log "Warning: continuing without top-level 'CLI' importable; some entrypoints may fail." "ERROR" }
|
|
|
|
|
} else { Write-Log "Top-level 'CLI' not importable and cannot prompt (quiet mode); aborting." "ERROR"; exit 6 }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Unable to determine site-packages to write .pth; falling back to editable install prompt" "WARNING"
|
|
|
|
|
if ($Editable) { try { & $venvPython -m pip install -e . } catch { Write-Log "Editable install failed: $_" "ERROR"; exit 6 } }
|
|
|
|
|
elseif (-not $Quiet) {
|
|
|
|
|
$ans = Read-Host "Top-level 'CLI' not importable; install project in editable mode now? (Y/n)"
|
|
|
|
|
if ($ans -eq 'y' -or $ans -eq 'Y') { try { & $venvPython -m pip install -e . } catch { Write-Log "Editable install failed: $_" "ERROR"; exit 6 } }
|
|
|
|
|
} else { Write-Log "Top-level 'CLI' not importable and cannot prompt (quiet mode); aborting." "ERROR"; exit 6 }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Failed to verify top-level 'CLI': $_" "ERROR"
|
|
|
|
|
exit 6
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 02:13:21 -08:00
|
|
|
# Install Playwright browsers (default: chromium) unless explicitly disabled
|
|
|
|
|
if (-not $NoPlaywright) {
|
|
|
|
|
Write-Log "Ensuring Playwright browsers are installed (browsers=$PlaywrightBrowsers)..."
|
|
|
|
|
try {
|
|
|
|
|
& $venvPython -c "import importlib; importlib.import_module('playwright')" 2>$null
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
Write-Log "'playwright' package not found in venv; installing via pip..."
|
|
|
|
|
& $venvPython -m pip install playwright
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Failed to check/install 'playwright' package: $_" "ERROR"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if ($PlaywrightBrowsers -eq 'all') {
|
|
|
|
|
Write-Log "Installing all Playwright browsers..."
|
|
|
|
|
& $venvPython -m playwright install
|
|
|
|
|
} else {
|
|
|
|
|
$list = $PlaywrightBrowsers -split ','
|
|
|
|
|
foreach ($b in $list) {
|
|
|
|
|
$btrim = $b.Trim()
|
|
|
|
|
if ($btrim) {
|
|
|
|
|
Write-Log "Installing Playwright browser: $btrim"
|
|
|
|
|
& $venvPython -m playwright install $btrim
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Playwright browser install failed: $_" "ERROR"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Verify environment for known package conflicts (urllib3 compatibility)
|
|
|
|
|
Write-Log "Verifying environment for known package conflicts (urllib3 compatibility)..."
|
|
|
|
|
try {
|
|
|
|
|
& $venvPython -c "import sys; from SYS.env_check import check_urllib3_compat; ok, msg = check_urllib3_compat(); print(msg); sys.exit(0 if ok else 2)"
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
Write-Log "Bootstrap detected a potentially broken 'urllib3' installation. See message above." "ERROR"
|
|
|
|
|
Write-Log "Suggested fixes (activate the venv first):" "INFO"
|
|
|
|
|
Write-Log " $ $venvPython -m pip uninstall urllib3-future -y" "INFO"
|
|
|
|
|
Write-Log " $ $venvPython -m pip install --upgrade --force-reinstall urllib3" "INFO"
|
|
|
|
|
Write-Log " $ $venvPython -m pip install niquests -U" "INFO"
|
2025-12-24 02:32:15 -08:00
|
|
|
|
2025-12-24 02:42:13 -08:00
|
|
|
function Get-SitePackages {
|
|
|
|
|
param($python)
|
|
|
|
|
try {
|
|
|
|
|
$json = & $python -c "import site, sysconfig, json; p=[];
|
|
|
|
|
try:
|
|
|
|
|
p.extend(site.getsitepackages())
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
|
|
|
|
pp = sysconfig.get_paths().get('purelib')
|
|
|
|
|
if pp:
|
|
|
|
|
p.append(pp)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
seen = []
|
|
|
|
|
out = []
|
|
|
|
|
for s in p:
|
|
|
|
|
if s and s not in seen:
|
|
|
|
|
seen.append(s)
|
|
|
|
|
out.append(s)
|
|
|
|
|
print(json.dumps(out))"
|
|
|
|
|
return $json | ConvertFrom-Json
|
|
|
|
|
} catch {
|
|
|
|
|
return @()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Find-InterferingPth {
|
|
|
|
|
param($python)
|
|
|
|
|
$pths = @()
|
|
|
|
|
$sps = Get-SitePackages -python $python
|
|
|
|
|
foreach ($sp in $sps) {
|
|
|
|
|
if (Test-Path $sp) {
|
|
|
|
|
Get-ChildItem -Path $sp -Filter *.pth -File -ErrorAction SilentlyContinue | ForEach-Object {
|
|
|
|
|
$c = Get-Content -Path $_.FullName -ErrorAction SilentlyContinue | Out-String
|
|
|
|
|
if ($c -match 'urllib3_future' -or $c -match 'urllib3-future') {
|
|
|
|
|
$pths += $_.FullName
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $pths
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Helper to try removal and re-verify
|
|
|
|
|
function RemovePthAndVerify {
|
|
|
|
|
param($python, $paths)
|
|
|
|
|
foreach ($p in $paths) { Remove-Item -Force $p -ErrorAction SilentlyContinue }
|
|
|
|
|
try { & $python -m pip install --upgrade --force-reinstall urllib3 } catch { Write-Log "pip install failed: $_" "ERROR"; return $false }
|
|
|
|
|
& $python -c "import sys; from SYS.env_check import check_urllib3_compat; ok, msg = check_urllib3_compat(); print(msg); sys.exit(0 if ok else 2)"
|
|
|
|
|
return ($LASTEXITCODE -eq 0)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 02:32:15 -08:00
|
|
|
if ($FixUrllib3) {
|
|
|
|
|
Write-Log "Attempting automatic fix (--FixUrllib3)..." "INFO"
|
|
|
|
|
try { & $venvPython -m pip uninstall urllib3-future -y } catch {}
|
|
|
|
|
try { & $venvPython -m pip install --upgrade --force-reinstall urllib3 } catch { Write-Log "pip install failed: $_" "ERROR"; exit 7 }
|
|
|
|
|
try { & $venvPython -m pip install niquests -U } catch { Write-Log "pip install niquests failed: $_" "ERROR" }
|
|
|
|
|
& $venvPython -c "import sys; from SYS.env_check import check_urllib3_compat; ok, msg = check_urllib3_compat(); print(msg); sys.exit(0 if ok else 2)"
|
2025-12-24 02:42:13 -08:00
|
|
|
if ($LASTEXITCODE -eq 0) {
|
2025-12-24 02:32:15 -08:00
|
|
|
Write-Log "Success: urllib3 problems appear resolved; continuing." "INFO"
|
2025-12-24 02:42:13 -08:00
|
|
|
} else {
|
|
|
|
|
Write-Log "Initial automatic fix did not resolve the issue; searching for interfering .pth files..." "INFO"
|
|
|
|
|
$pths = Find-InterferingPth -python $venvPython
|
|
|
|
|
if ($pths.Count -eq 0) {
|
|
|
|
|
Write-Log "No interfering .pth files found; aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
Write-Log ("Found interfering .pth files:`n" + ($pths -join "`n")) "ERROR"
|
|
|
|
|
if ($RemovePth) {
|
|
|
|
|
Write-Log "Removing .pth files as requested..." "INFO"
|
|
|
|
|
if (RemovePthAndVerify -python $venvPython -paths $pths) {
|
|
|
|
|
Write-Log "Success: urllib3 problems resolved after .pth removal; continuing." "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Automatic fix failed even after .pth removal; aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if ($Quiet) { Write-Log "Detected interfering .pth files but cannot prompt in quiet mode. Use -RemovePth to remove them automatically." "ERROR"; exit 7 }
|
|
|
|
|
$ans = Read-Host "Remove these files now? (y/N)"
|
|
|
|
|
if ($ans -eq 'y' -or $ans -eq 'Y') {
|
|
|
|
|
if (RemovePthAndVerify -python $venvPython -paths $pths) {
|
|
|
|
|
Write-Log "Success: urllib3 problems resolved after .pth removal; continuing." "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Automatic fix failed even after .pth removal; aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "User declined to remove .pth files. Aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-24 02:32:15 -08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if ($Quiet) {
|
|
|
|
|
Write-Log "Bootstrap detected a potentially broken 'urllib3' installation. Use -FixUrllib3 to attempt an automatic fix." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
$ans = Read-Host "Attempt automatic fix now? (y/N)"
|
|
|
|
|
if ($ans -eq 'y' -or $ans -eq 'Y') {
|
2025-12-24 02:59:42 -08:00
|
|
|
$AutoFixInteractive = $true
|
2025-12-24 02:32:15 -08:00
|
|
|
try { & $venvPython -m pip uninstall urllib3-future -y } catch {}
|
|
|
|
|
try { & $venvPython -m pip install --upgrade --force-reinstall urllib3 } catch { Write-Log "pip install failed: $_" "ERROR"; exit 7 }
|
|
|
|
|
try { & $venvPython -m pip install niquests -U } catch { Write-Log "pip install niquests failed: $_" "ERROR" }
|
|
|
|
|
& $venvPython -c "import sys; from SYS.env_check import check_urllib3_compat; ok, msg = check_urllib3_compat(); print(msg); sys.exit(0 if ok else 2)"
|
2025-12-24 02:42:13 -08:00
|
|
|
if ($LASTEXITCODE -eq 0) {
|
2025-12-24 02:32:15 -08:00
|
|
|
Write-Log "Success: urllib3 problems appear resolved; continuing." "INFO"
|
2025-12-24 02:42:13 -08:00
|
|
|
} else {
|
|
|
|
|
Write-Log "Initial automatic fix did not resolve the issue; searching for interfering .pth files..." "INFO"
|
|
|
|
|
$pths = Find-InterferingPth -python $venvPython
|
|
|
|
|
if ($pths.Count -eq 0) {
|
|
|
|
|
Write-Log "No interfering .pth files found; aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
Write-Log ("Found interfering .pth files:`n" + ($pths -join "`n")) "ERROR"
|
2025-12-24 02:59:42 -08:00
|
|
|
if ($RemovePth -or $AutoFixInteractive -or $FixUrllib3) {
|
|
|
|
|
Write-Log "Removing .pth files automatically..." "INFO"
|
2025-12-24 02:42:13 -08:00
|
|
|
if (RemovePthAndVerify -python $venvPython -paths $pths) {
|
|
|
|
|
Write-Log "Success: urllib3 problems resolved after .pth removal; continuing." "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Automatic fix failed even after .pth removal; aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$ans2 = Read-Host "Remove these files now? (y/N)"
|
|
|
|
|
if ($ans2 -eq 'y' -or $ans2 -eq 'Y') {
|
|
|
|
|
if (RemovePthAndVerify -python $venvPython -paths $pths) {
|
|
|
|
|
Write-Log "Success: urllib3 problems resolved after .pth removal; continuing." "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Automatic fix failed even after .pth removal; aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "User declined to remove .pth files. Aborting." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-24 02:32:15 -08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Aborting bootstrap to avoid leaving a broken environment." "ERROR"
|
|
|
|
|
exit 7
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-24 02:13:21 -08:00
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Failed to run environment verification: $_" "ERROR"
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 16:36:39 -08:00
|
|
|
Write-Log "Deno is already installed: $($denoCmd.Path)"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Installing Deno via official installer (https://deno.land)"
|
|
|
|
|
try {
|
|
|
|
|
try {
|
|
|
|
|
irm https://deno.land/install.ps1 | iex
|
|
|
|
|
} catch {
|
|
|
|
|
iwr https://deno.land/install.ps1 -UseBasicParsing | iex
|
|
|
|
|
}
|
|
|
|
|
# Ensure common install locations are on PATH for this session
|
|
|
|
|
$denoCandidatePaths = @(
|
|
|
|
|
Join-Path $env:USERPROFILE ".deno\bin",
|
|
|
|
|
Join-Path $env:LOCALAPPDATA "deno\bin"
|
|
|
|
|
)
|
|
|
|
|
foreach ($p in $denoCandidatePaths) {
|
|
|
|
|
if (Test-Path $p) {
|
|
|
|
|
if ($env:PATH -notmatch [regex]::Escape($p)) {
|
|
|
|
|
$env:PATH = $env:PATH + ";" + $p
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$v = & deno --version 2>$null
|
|
|
|
|
if ($v) {
|
|
|
|
|
Write-Log "Deno installed: $v"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "Deno installer completed but 'deno' not found on PATH; you may need to restart your shell or add the Deno bin folder to PATH." "ERROR"
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Deno install failed: $_" "ERROR"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Shortcuts (Windows only)
|
|
|
|
|
if ($IsWindowsPlatform) {
|
|
|
|
|
if ($CreateDesktopShortcut -or $CreateStartMenuShortcut) {
|
|
|
|
|
$wsh = New-Object -ComObject WScript.Shell
|
|
|
|
|
$mmExe = Join-Path $venvFull "Scripts\mm.exe"
|
|
|
|
|
$target = $null
|
|
|
|
|
$args = ""
|
|
|
|
|
if (Test-Path $mmExe) {
|
|
|
|
|
$target = $mmExe
|
|
|
|
|
} else {
|
|
|
|
|
$target = $venvPython
|
|
|
|
|
$args = "-m medeia_macina.cli_entry"
|
|
|
|
|
}
|
|
|
|
|
if ($CreateDesktopShortcut) {
|
|
|
|
|
$desk = [Environment]::GetFolderPath('Desktop')
|
|
|
|
|
$link = Join-Path $desk "Medeia-Macina.lnk"
|
|
|
|
|
Write-Log "Creating Desktop shortcut: $link"
|
|
|
|
|
$sc = $wsh.CreateShortcut($link)
|
|
|
|
|
$sc.TargetPath = $target
|
|
|
|
|
$sc.Arguments = $args
|
|
|
|
|
$sc.WorkingDirectory = $repoRoot
|
|
|
|
|
$sc.IconLocation = "$target,0"
|
|
|
|
|
$sc.Save()
|
|
|
|
|
}
|
|
|
|
|
if ($CreateStartMenuShortcut) {
|
|
|
|
|
$start = Join-Path ([Environment]::GetFolderPath('ApplicationData')) 'Microsoft\Windows\Start Menu\Programs'
|
|
|
|
|
$dir = Join-Path $start "Medeia-Macina"
|
|
|
|
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
|
|
|
|
$link2 = Join-Path $dir "Medeia-Macina.lnk"
|
|
|
|
|
Write-Log "Creating Start Menu shortcut: $link2"
|
|
|
|
|
$sc2 = $wsh.CreateShortcut($link2)
|
|
|
|
|
$sc2.TargetPath = $target
|
|
|
|
|
$sc2.Arguments = $args
|
|
|
|
|
$sc2.WorkingDirectory = $repoRoot
|
|
|
|
|
$sc2.IconLocation = "$target,0"
|
|
|
|
|
$sc2.Save()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Install global 'mm' launcher into the user's bin directory so it can be invoked from any shell.
|
|
|
|
|
try {
|
|
|
|
|
$globalBin = Join-Path $env:USERPROFILE 'bin'
|
|
|
|
|
New-Item -ItemType Directory -Path $globalBin -Force | Out-Null
|
|
|
|
|
|
|
|
|
|
$mmCmd = Join-Path $globalBin 'mm.cmd'
|
|
|
|
|
$mmPs1 = Join-Path $globalBin 'mm.ps1'
|
|
|
|
|
|
|
|
|
|
$repo = $repoRoot
|
|
|
|
|
|
|
|
|
|
$cmdText = @"
|
|
|
|
|
@echo off
|
|
|
|
|
set "REPO=__REPO__"
|
2025-12-24 03:50:10 -08:00
|
|
|
if exist "%REPO%\.venv\Scripts\mm.exe" "%REPO%\.venv\Scripts\mm.exe" %*
|
|
|
|
|
if defined MM_DEBUG (
|
|
|
|
|
echo MM_DEBUG: REPO=%REPO%
|
|
|
|
|
if exist "%REPO%\.venv\Scripts\python.exe" (
|
|
|
|
|
"%REPO%\.venv\Scripts\python.exe" -c "import sys,importlib,importlib.util; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);"
|
|
|
|
|
) else (
|
|
|
|
|
python -c "import sys,importlib,importlib.util; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);"
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-12-24 02:13:21 -08:00
|
|
|
if exist "%REPO%\.venv\Scripts\python.exe" (
|
2025-12-24 03:50:10 -08:00
|
|
|
"%REPO%\.venv\Scripts\python.exe" -m medeia_macina.cli_entry %*
|
2025-12-23 16:36:39 -08:00
|
|
|
exit /b %ERRORLEVEL%
|
|
|
|
|
)
|
2025-12-24 02:13:21 -08:00
|
|
|
if exist "%REPO%\CLI.py" (
|
|
|
|
|
python "%REPO%\CLI.py" %*
|
2025-12-23 16:36:39 -08:00
|
|
|
exit /b %ERRORLEVEL%
|
|
|
|
|
)
|
|
|
|
|
python -m medeia_macina.cli_entry %*
|
|
|
|
|
"@
|
|
|
|
|
# Inject actual repo path safely (escape double-quotes if any)
|
|
|
|
|
$cmdText = $cmdText.Replace('__REPO__', $repo.Replace('"', '""'))
|
|
|
|
|
if (Test-Path $mmCmd) {
|
|
|
|
|
$bak = "$mmCmd.bak$(Get-Date -UFormat %s)"
|
|
|
|
|
Move-Item -Path $mmCmd -Destination $bak -Force
|
|
|
|
|
}
|
|
|
|
|
Set-Content -LiteralPath $mmCmd -Value $cmdText -Encoding UTF8
|
|
|
|
|
|
|
|
|
|
# PowerShell shim: use single-quoted here-string so literal PowerShell variables
|
|
|
|
|
# (like $args) are not expanded by this script when writing the file.
|
|
|
|
|
$ps1Text = @'
|
|
|
|
|
Param([Parameter(ValueFromRemainingArguments=$true)] $args)
|
|
|
|
|
$repo = "__REPO__"
|
|
|
|
|
$venv = Join-Path $repo '.venv'
|
2025-12-24 03:50:10 -08:00
|
|
|
$exe = Join-Path $venv 'Scripts\mm.exe'
|
|
|
|
|
if (Test-Path $exe) { & $exe @args; exit $LASTEXITCODE }
|
2025-12-23 16:36:39 -08:00
|
|
|
$py = Join-Path $venv 'Scripts\python.exe'
|
2025-12-24 03:50:10 -08:00
|
|
|
if ($env:MM_DEBUG) {
|
|
|
|
|
Write-Host "MM_DEBUG: diagnostics" -ForegroundColor Yellow
|
|
|
|
|
if (Test-Path $py) { & $py -c "import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);" }
|
|
|
|
|
else { python -c "import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);" }
|
|
|
|
|
}
|
|
|
|
|
if (Test-Path $py) { & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE }
|
|
|
|
|
if (Test-Path (Join-Path $repo 'CLI.py')) { & python (Join-Path $repo 'CLI.py') @args; exit $LASTEXITCODE }
|
2025-12-23 16:36:39 -08:00
|
|
|
# fallback
|
2025-12-24 03:50:10 -08:00
|
|
|
python -m medeia_macina.cli_entry @args
|
2025-12-23 16:36:39 -08:00
|
|
|
'@
|
|
|
|
|
# Inject the actual repo path safely (escape embedded double-quotes if any)
|
|
|
|
|
$ps1Text = $ps1Text.Replace('__REPO__', $repo.Replace('"', '""'))
|
|
|
|
|
# Ensure the PowerShell shim falls back to the correct module when the venv isn't present
|
|
|
|
|
$ps1Text = $ps1Text.Replace(' -m medeia_entry ', ' -m medeia_macina.cli_entry ')
|
|
|
|
|
$ps1Text = $ps1Text.Replace('python -m medeia_entry', 'python -m medeia_macina.cli_entry')
|
|
|
|
|
if (Test-Path $mmPs1) {
|
|
|
|
|
$bak = "$mmPs1.bak$(Get-Date -UFormat %s)"
|
|
|
|
|
Move-Item -Path $mmPs1 -Destination $bak -Force
|
|
|
|
|
}
|
|
|
|
|
Set-Content -LiteralPath $mmPs1 -Value $ps1Text -Encoding UTF8
|
|
|
|
|
|
|
|
|
|
# Ensure user's bin is on PATH (User env var)
|
|
|
|
|
try {
|
|
|
|
|
$cur = [Environment]::GetEnvironmentVariable('PATH', 'User')
|
|
|
|
|
if ($cur -notlike "*$globalBin*") {
|
|
|
|
|
if ($cur) { $new = ($globalBin + ';' + $cur) } else { $new = $globalBin }
|
|
|
|
|
[Environment]::SetEnvironmentVariable('PATH', $new, 'User')
|
|
|
|
|
# Update current session PATH for immediate use
|
|
|
|
|
$env:PATH = $globalBin + ';' + $env:PATH
|
|
|
|
|
Write-Log "Added $globalBin to User PATH. Restart your shell to pick this up." "INFO"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Log "$globalBin is already on the User PATH" "INFO"
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Failed to update user PATH: $_" "ERROR"
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
Write-Log "Failed to install global launcher: $_" "ERROR"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Log "Bootstrap complete." "INFO"
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "To activate the venv:"
|
|
|
|
|
if ($IsWindowsPlatform) {
|
|
|
|
|
Write-Host " PS> .\$VenvPath\Scripts\Activate.ps1"
|
|
|
|
|
Write-Host " CMD> .\$VenvPath\Scripts\activate.bat"
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host " $ source ./$VenvPath/bin/activate"
|
|
|
|
|
}
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "To run the app:"
|
|
|
|
|
Write-Host " $ .\$VenvPath\Scripts\mm.exe (Windows) or"
|
|
|
|
|
Write-Host " $ ./$VenvPath/bin/mm (Linux) or"
|
|
|
|
|
Write-Host " $ $venvPython -m medeia_macina.cli_entry"
|
2025-12-24 03:50:10 -08:00
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "If the global 'mm' launcher fails, collect runtime diagnostics by setting MM_DEBUG and re-running the command:""
|
|
|
|
|
if ($IsWindowsPlatform) { Write-Host " PowerShell: $env:MM_DEBUG = '1'; mm" } else { Write-Host " POSIX: MM_DEBUG=1 mm" }
|