512 lines
15 KiB
PowerShell
512 lines
15 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Build MiroFish Windows installer
|
|
|
|
.DESCRIPTION
|
|
This script builds a Windows installer (.exe) for MiroFish.
|
|
Supports embedded Python mode (smaller) or PyInstaller mode (larger but self-contained).
|
|
|
|
.PARAMETER PyInstaller
|
|
Use PyInstaller mode instead of embedded Python mode
|
|
|
|
.PARAMETER SkipFrontend
|
|
Skip frontend build (use if unchanged)
|
|
|
|
.PARAMETER SkipBackend
|
|
Skip backend processing (use if unchanged)
|
|
|
|
.PARAMETER SkipInstaller
|
|
Skip installer creation (only generate executables)
|
|
|
|
.PARAMETER Clean
|
|
Clean old builds before starting
|
|
|
|
.PARAMETER Version
|
|
Version number for the installer (default: 0.1.1)
|
|
|
|
.EXAMPLE
|
|
.\build.ps1
|
|
|
|
.EXAMPLE
|
|
.\build.ps1 -PyInstaller -Clean
|
|
|
|
.EXAMPLE
|
|
.\build.ps1 -SkipFrontend -SkipBackend
|
|
#>
|
|
|
|
param(
|
|
[switch]$PyInstaller,
|
|
[switch]$SkipFrontend,
|
|
[switch]$SkipBackend,
|
|
[switch]$SkipInstaller,
|
|
[switch]$Clean,
|
|
[string]$Version = "0.1.1"
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$RootDir = Split-Path -Parent $ScriptDir
|
|
$DistDir = Join-Path $RootDir "dist"
|
|
$BuildDir = Join-Path $RootDir "build"
|
|
|
|
# Colors for output
|
|
function Write-Info { param($msg) Write-Host "[INFO] $msg" -ForegroundColor Cyan }
|
|
function Write-Success { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green }
|
|
function Write-Warning { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow }
|
|
function Write-Error { param($msg) Write-Host "[ERROR] $msg" -ForegroundColor Red }
|
|
|
|
# Check prerequisites
|
|
function Test-Prerequisites {
|
|
Write-Info "Checking prerequisites..."
|
|
|
|
# Check Python
|
|
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
|
if (-not $pythonCmd) {
|
|
Write-Error "Python not found. Please install Python 3.9+ and add to PATH."
|
|
exit 1
|
|
}
|
|
$pythonVersion = & python --version 2>&1
|
|
Write-Info "Found $pythonVersion"
|
|
|
|
# Check Node.js
|
|
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
|
if (-not $nodeCmd) {
|
|
Write-Error "Node.js not found. Please install Node.js 18+ and add to PATH."
|
|
exit 1
|
|
}
|
|
$nodeVersion = & node --version 2>&1
|
|
Write-Info "Found Node.js $nodeVersion"
|
|
|
|
# Check Inno Setup (optional)
|
|
$isccCmd = Get-Command iscc -ErrorAction SilentlyContinue
|
|
if (-not $isccCmd) {
|
|
Write-Warning "Inno Setup not found. Installer creation will be skipped."
|
|
Write-Warning "Install from: https://jrsoftware.org/isdl.php"
|
|
} else {
|
|
Write-Info "Found Inno Setup"
|
|
}
|
|
|
|
if ($PyInstaller) {
|
|
# Check PyInstaller
|
|
$pyinstallerCmd = Get-Command pyinstaller -ErrorAction SilentlyContinue
|
|
if (-not $pyinstallerCmd) {
|
|
Write-Info "Installing PyInstaller..."
|
|
& pip install pyinstaller
|
|
}
|
|
}
|
|
|
|
Write-Success "Prerequisites check passed"
|
|
}
|
|
|
|
# Clean old builds
|
|
function Clear-Build {
|
|
Write-Info "Cleaning old builds..."
|
|
|
|
if (Test-Path $DistDir) {
|
|
Remove-Item $DistDir -Recurse -Force
|
|
Write-Info "Removed $DistDir"
|
|
}
|
|
|
|
if (Test-Path $BuildDir) {
|
|
Remove-Item $BuildDir -Recurse -Force
|
|
Write-Info "Removed $BuildDir"
|
|
}
|
|
|
|
# Clean Python cache
|
|
Get-ChildItem -Path $RootDir -Directory -Recurse -Filter "__pycache__" | Remove-Item -Recurse -Force
|
|
Get-ChildItem -Path $RootDir -Filter "*.pyc" -Recurse | Remove-Item -Force
|
|
|
|
# Clean Node modules cache
|
|
$frontendDir = Join-Path $RootDir "frontend"
|
|
$nodeModulesDir = Join-Path $frontendDir "node_modules"
|
|
if (Test-Path (Join-Path $frontendDir ".vite")) {
|
|
Remove-Item (Join-Path $frontendDir ".vite") -Recurse -Force
|
|
}
|
|
|
|
Write-Success "Clean completed"
|
|
}
|
|
|
|
# Build frontend
|
|
function Build-Frontend {
|
|
if ($SkipFrontend) {
|
|
Write-Info "Skipping frontend build (as requested)"
|
|
return
|
|
}
|
|
|
|
Write-Info "Building frontend..."
|
|
|
|
$frontendDir = Join-Path $RootDir "frontend"
|
|
Push-Location $frontendDir
|
|
|
|
try {
|
|
# Install dependencies
|
|
Write-Info "Installing frontend dependencies..."
|
|
& npm ci
|
|
|
|
# Build production version
|
|
Write-Info "Building production frontend..."
|
|
& npm run build
|
|
|
|
Write-Success "Frontend build completed"
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
# Build backend
|
|
function Build-Backend {
|
|
if ($SkipBackend) {
|
|
Write-Info "Skipping backend processing (as requested)"
|
|
return
|
|
}
|
|
|
|
Write-Info "Building backend..."
|
|
|
|
$backendDir = Join-Path $RootDir "backend"
|
|
|
|
# Install Python dependencies
|
|
Write-Info "Installing Python dependencies..."
|
|
Push-Location $backendDir
|
|
try {
|
|
& pip install -r requirements.txt --target ./libs -q
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
|
|
if ($PyInstaller) {
|
|
Build-PyInstaller
|
|
} else {
|
|
Build-Embedded
|
|
}
|
|
|
|
Write-Success "Backend build completed"
|
|
}
|
|
|
|
# Build with PyInstaller
|
|
function Build-PyInstaller {
|
|
Write-Info "Building with PyInstaller..."
|
|
|
|
$backendDir = Join-Path $RootDir "backend"
|
|
$distBackendDir = Join-Path $DistDir "backend"
|
|
|
|
Push-Location $backendDir
|
|
try {
|
|
# Create PyInstaller spec
|
|
$specContent = @"
|
|
# -*- mode: python ; coding: utf-8 -*-
|
|
|
|
block_cipher = None
|
|
|
|
a = Analysis(
|
|
['app.py'],
|
|
pathex=[],
|
|
binaries=[],
|
|
datas=[
|
|
('static', 'static'),
|
|
('templates', 'templates'),
|
|
],
|
|
hiddenimports=['flask', 'flask_cors', 'gunicorn'],
|
|
hookspath=[],
|
|
hooksconfig={},
|
|
runtime_hooks=[],
|
|
excludes=[],
|
|
win_no_prefer_redirects=False,
|
|
win_private_assemblies=False,
|
|
cipher=block_cipher,
|
|
noarchive=False,
|
|
)
|
|
|
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
|
|
|
exe = EXE(
|
|
pyz,
|
|
a.scripts,
|
|
a.binaries,
|
|
a.zipfiles,
|
|
a.datas,
|
|
[],
|
|
name='MiroFish',
|
|
debug=False,
|
|
bootloader_ignore_signals=False,
|
|
strip=False,
|
|
upx=True,
|
|
upx_exclude=[],
|
|
runtime_tmpdir=None,
|
|
console=True,
|
|
disable_windowed_traceback=False,
|
|
argv_emulation=False,
|
|
target_arch=None,
|
|
codesign_identity=None,
|
|
entitlements_file=None,
|
|
)
|
|
"@
|
|
|
|
$specContent | Out-File -FilePath "mirofish.spec" -Encoding UTF8
|
|
|
|
# Run PyInstaller
|
|
& pyinstaller mirofish.spec --distpath $distBackendDir --workpath $BuildDir --clean
|
|
|
|
Write-Success "PyInstaller build completed"
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
# Build with embedded Python
|
|
function Build-Embedded {
|
|
Write-Info "Building with embedded Python..."
|
|
|
|
$backendDir = Join-Path $RootDir "backend"
|
|
$distBackendDir = Join-Path $DistDir "MiroFish_Portable\backend"
|
|
|
|
# Create dist directory
|
|
New-Item -ItemType Directory -Force -Path $distBackendDir | Out-Null
|
|
|
|
# Copy backend files
|
|
Write-Info "Copying backend files..."
|
|
Copy-Item -Path "$backendDir\*" -Destination $distBackendDir -Recurse -Force
|
|
|
|
# Download embedded Python
|
|
$embeddedDir = Join-Path $ScriptDir "embedded"
|
|
$pythonZip = Join-Path $embeddedDir "python.zip"
|
|
|
|
if (-not (Test-Path $pythonZip)) {
|
|
Write-Info "Downloading embedded Python..."
|
|
New-Item -ItemType Directory -Force -Path $embeddedDir | Out-Null
|
|
|
|
# Get Python version
|
|
$pyVersion = (& python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')").Trim()
|
|
$pythonUrl = "https://www.python.org/ftp/python/$pyVersion.0/python-$pyVersion.0-embed-amd64.zip"
|
|
|
|
try {
|
|
Invoke-WebRequest -Uri $pythonUrl -OutFile $pythonZip
|
|
Write-Success "Downloaded embedded Python"
|
|
} catch {
|
|
Write-Error "Failed to download embedded Python: $_"
|
|
Write-Info "Please download manually from: $pythonUrl"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Extract embedded Python
|
|
$pythonDir = Join-Path $distBackendDir "python"
|
|
New-Item -ItemType Directory -Force -Path $pythonDir | Out-Null
|
|
Expand-Archive -Path $pythonZip -DestinationPath $pythonDir -Force
|
|
|
|
# Install dependencies to embedded Python
|
|
Write-Info "Installing dependencies to embedded Python..."
|
|
$pipDir = Join-Path $pythonDir "Lib\site-packages"
|
|
New-Item -ItemType Directory -Force -Path $pipDir | Out-Null
|
|
|
|
Get-Content "$backendDir\requirements.txt" | ForEach-Object {
|
|
if ($_ -match "^\s*#" -or $_ -match "^\s*$") { return }
|
|
& pip install $_ --target $pipDir -q 2>&1 | Out-Null
|
|
}
|
|
|
|
Write-Success "Embedded Python build completed"
|
|
}
|
|
|
|
# Create launcher script
|
|
function New-Launcher {
|
|
Write-Info "Creating launcher script..."
|
|
|
|
$portableDir = Join-Path $DistDir "MiroFish_Portable"
|
|
$launcherScript = Join-Path $portableDir "start.ps1"
|
|
|
|
$scriptContent = @'
|
|
# MiroFish Launcher Script
|
|
param(
|
|
[string]$ApiKey = "",
|
|
[int]$Port = 5000
|
|
)
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$BackendDir = Join-Path $ScriptDir "backend"
|
|
$FrontendDir = Join-Path $ScriptDir "frontend"
|
|
$PythonExe = Join-Path $BackendDir "python\python.exe"
|
|
|
|
# Set environment variables
|
|
$env:MIROFISH_PORT = $Port
|
|
if ($ApiKey) {
|
|
$env:MIROFISH_API_KEY = $ApiKey
|
|
}
|
|
|
|
Write-Host "Starting MiroFish..." -ForegroundColor Cyan
|
|
Write-Host "Backend: http://localhost:$Port" -ForegroundColor Green
|
|
Write-Host "Frontend: http://localhost:5173" -ForegroundColor Green
|
|
|
|
# Start backend
|
|
$backendJob = Start-Job -ScriptBlock {
|
|
param($PythonExe, $BackendDir)
|
|
Set-Location $BackendDir
|
|
& $PythonExe app.py
|
|
} -ArgumentList $PythonExe, $BackendDir
|
|
|
|
# Start frontend dev server
|
|
$frontendJob = Start-Job -ScriptBlock {
|
|
param($FrontendDir)
|
|
Set-Location $FrontendDir
|
|
npm run dev -- --host
|
|
} -ArgumentList $FrontendDir
|
|
|
|
# Open browser
|
|
Start-Sleep -Seconds 3
|
|
Start-Process "http://localhost:5173"
|
|
|
|
# Wait for jobs
|
|
Wait-Job $backendJob, $frontendJob
|
|
'@
|
|
|
|
$scriptContent | Out-File -FilePath $launcherScript -Encoding UTF8
|
|
|
|
# Create batch file for easy launch
|
|
$batchFile = Join-Path $portableDir "start.bat"
|
|
"@echo off`npowershell -ExecutionPolicy Bypass -File `"%~dp0start.ps1`" %*" | Out-File -FilePath $batchFile -Encoding ASCII
|
|
|
|
# Copy frontend dist
|
|
$frontendDistDir = Join-Path $RootDir "frontend\dist"
|
|
if (Test-Path $frontendDistDir) {
|
|
Copy-Item -Path $frontendDistDir -Destination (Join-Path $portableDir "frontend\dist") -Recurse -Force
|
|
}
|
|
|
|
Write-Success "Launcher script created"
|
|
}
|
|
|
|
# Create installer
|
|
function New-Installer {
|
|
if ($SkipInstaller) {
|
|
Write-Info "Skipping installer creation (as requested)"
|
|
return
|
|
}
|
|
|
|
$isccCmd = Get-Command iscc -ErrorAction SilentlyContinue
|
|
if (-not $isccCmd) {
|
|
Write-Warning "Inno Setup not found, skipping installer creation"
|
|
Write-Warning "Portable version is available in: $DistDir\MiroFish_Portable"
|
|
return
|
|
}
|
|
|
|
Write-Info "Creating installer..."
|
|
|
|
$setupIss = Join-Path $ScriptDir "setup.iss"
|
|
|
|
# Generate setup.iss
|
|
$issContent = @"
|
|
[Setup]
|
|
AppName=MiroFish
|
|
AppVersion=$Version
|
|
AppPublisher=MiroFish Team
|
|
AppPublisherURL=https://github.com/666ghj/MiroFish
|
|
AppSupportURL=https://github.com/666ghj/MiroFish/issues
|
|
DefaultDirName={autopf}\MiroFish
|
|
DefaultGroupName=MiroFish
|
|
AllowNoIcons=yes
|
|
LicenseFile=..\LICENSE
|
|
InfoBeforeFile=README.txt
|
|
OutputDir=..\dist
|
|
OutputBaseFilename=MiroFish_Setup_$Version
|
|
SetupIconFile=assets\icon.ico
|
|
Compression=lzma2/ultra64
|
|
SolidCompression=yes
|
|
WizardStyle=modern
|
|
PrivilegesRequired=admin
|
|
|
|
[Languages]
|
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl"
|
|
|
|
[Tasks]
|
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
|
|
[Files]
|
|
Source: "..\dist\MiroFish_Portable\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
|
|
|
[Icons]
|
|
Name: "{group}\MiroFish"; Filename: "{app}\start.bat"
|
|
Name: "{group}\{cm:ProgramOnTheWeb,MiroFish}"; Filename: "https://github.com/666ghj/MiroFish"
|
|
Name: "{group}\{cm:UninstallProgram,MiroFish}"; Filename: "{uninstallexe}"
|
|
Name: "{autodesktop}\MiroFish"; Filename: "{app}\start.bat"; Tasks: desktopicon
|
|
|
|
[Run]
|
|
Filename: "{app}\start.bat"; Description: "{cm:LaunchProgram,MiroFish}"; Flags: nowait postinstall skipifsilent
|
|
|
|
[Code]
|
|
var
|
|
ApiKeyPage: TInputQueryWizardPage;
|
|
|
|
procedure InitializeWizard;
|
|
begin
|
|
ApiKeyPage := CreateInputQueryPage(wpWelcome,
|
|
'API Configuration', 'Enter your API key',
|
|
'Please enter your API key for LLM services. You can also configure this later in the .env file.');
|
|
ApiKeyPage.Add('API Key:', False);
|
|
end;
|
|
|
|
function GetApiKey(Param: String): String;
|
|
begin
|
|
Result := ApiKeyPage.Values[0];
|
|
end;
|
|
"@
|
|
|
|
$issContent | Out-File -FilePath $setupIss -Encoding UTF8
|
|
|
|
# Create README for installer
|
|
$readmeFile = Join-Path $ScriptDir "README.txt"
|
|
"MiroFish $Version`n`nThank you for installing MiroFish!`n`nAfter installation, you can launch the application from the Start Menu or Desktop shortcut.`n`nThe application will open in your default web browser.`n`nFor more information, visit: https://github.com/666ghj/MiroFish" | Out-File -FilePath $readmeFile -Encoding UTF8
|
|
|
|
# Create assets directory
|
|
$assetsDir = Join-Path $ScriptDir "assets"
|
|
New-Item -ItemType Directory -Force -Path $assetsDir | Out-Null
|
|
|
|
# Run Inno Setup
|
|
Push-Location $ScriptDir
|
|
try {
|
|
& iscc $setupIss
|
|
Write-Success "Installer created: $DistDir\MiroFish_Setup_$Version.exe"
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
# Main build process
|
|
function Main {
|
|
Write-Host ""
|
|
Write-Host "=====================================" -ForegroundColor Cyan
|
|
Write-Host " MiroFish Windows Installer Builder" -ForegroundColor Cyan
|
|
Write-Host "=====================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
Test-Prerequisites
|
|
|
|
if ($Clean) {
|
|
Clear-Build
|
|
}
|
|
|
|
# Create dist directory
|
|
if (-not (Test-Path $DistDir)) {
|
|
New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
|
|
}
|
|
|
|
Build-Frontend
|
|
Build-Backend
|
|
New-Launcher
|
|
New-Installer
|
|
|
|
Write-Host ""
|
|
Write-Host "=====================================" -ForegroundColor Green
|
|
Write-Host " Build completed successfully!" -ForegroundColor Green
|
|
Write-Host "=====================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
if (Test-Path (Join-Path $DistDir "MiroFish_Setup_$Version.exe")) {
|
|
Write-Host "Installer: $DistDir\MiroFish_Setup_$Version.exe" -ForegroundColor Cyan
|
|
}
|
|
|
|
Write-Host "Portable: $DistDir\MiroFish_Portable" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
}
|
|
|
|
# Run main
|
|
Main
|