Skip to content
Open
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
177 changes: 121 additions & 56 deletions scripts/start-windows.ps1
Original file line number Diff line number Diff line change
@@ -1,23 +1,80 @@
<#
.SYNOPSIS
Starts the Familiarise application on Windows.
Starts the Familiarise application on Windows (full rebuild).

.DESCRIPTION
Mirrors scripts/start-android.sh for Windows/PowerShell:
1. Frees port 8080
2. Cleans + rebuilds Flutter and regenerates code
3. Builds and starts the Dart Frog backend
4. Ensures an Android device/emulator is available, sets up adb reverse
5. Runs the Flutter app on the target device

.PARAMETER Port
Backend port (default 8080).

.PARAMETER Avd
Android Virtual Device name to launch when no device is connected (default Pixel_10).

.EXAMPLE
.\scripts\start-windows.ps1 -Avd Pixel_7_API_34
#>
param(
[int]$Port = 8080,
[string]$Avd = "Pixel_10"
)

$ErrorActionPreference = "Stop"

# Get the script directory and navigate to the project root
# Resolve project root from the script location and move there.
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location "$scriptDir\.."

Write-Host "=== Killing processes on port 8080 ===" -ForegroundColor Cyan
$port8080Pids = (Get-NetTCPConnection -LocalPort 8080 -ErrorAction SilentlyContinue).OwningProcess
if ($port8080Pids) {
foreach ($pidToKill in $port8080Pids) {
Stop-Process -Id $pidToKill -Force -ErrorAction SilentlyContinue
Write-Host "Killed process $pidToKill on port 8080"
$projectRoot = Resolve-Path "$scriptDir\.."
Set-Location $projectRoot

# Android SDK tool locations.
$sdkRoot = Join-Path $env:LOCALAPPDATA "Android\Sdk"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To support developers who have installed the Android SDK in a custom location, it is highly recommended to check the ANDROID_HOME and ANDROID_SDK_ROOT environment variables before falling back to the default %LOCALAPPDATA% path.

$sdkRoot = if ($env:ANDROID_HOME -and (Test-Path $env:ANDROID_HOME)) {
    $env:ANDROID_HOME
} elseif ($env:ANDROID_SDK_ROOT -and (Test-Path $env:ANDROID_SDK_ROOT)) {
    $env:ANDROID_SDK_ROOT
} else {
    Join-Path $env:LOCALAPPDATA "Android\Sdk"
}

$adbExe = Join-Path $sdkRoot "platform-tools\adb.exe"
$emulatorExe = Join-Path $sdkRoot "emulator\emulator.exe"

# --- Helpers -----------------------------------------------------------------

# Returns the id of the first fully-booted ("device" state) Android device,
# or $null if none are attached. Skips the "List of devices attached" header.
function Get-ConnectedAndroidDevice {
if (-not (Test-Path $adbExe)) { return $null }
$line = & $adbExe devices |
Select-Object -Skip 1 |
Where-Object { $_ -match '\bdevice$' } |
Select-Object -First 1
if ($line) { return ($line -split '\s+')[0] }
return $null
}

# Blocks until $Port accepts a TCP connection or the timeout elapses.
function Wait-ForPort {
param([int]$PortNumber, [int]$TimeoutSeconds = 30)
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
while ((Get-Date) -lt $deadline) {
if (Test-NetConnection -ComputerName "localhost" -Port $PortNumber -WarningAction SilentlyContinue -InformationLevel Quiet) {
return $true
}
Start-Sleep -Milliseconds 500
}
return $false
}
Comment on lines +54 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Test-NetConnection is notoriously slow when a port is closed because it performs additional diagnostics (like ICMP pings and DNS resolution) and has a long built-in timeout. This can cause the 30-second timeout to be reached with very few polling attempts. Using .NET's System.Net.Sockets.TcpClient is instantaneous and much more reliable for polling.

function Wait-ForPort {
    param([int]$PortNumber, [int]$TimeoutSeconds = 30)
    $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
    while ((Get-Date) -lt $deadline) {
        $client = New-Object System.Net.Sockets.TcpClient
        try {
            $client.Connect("localhost", $PortNumber)
            return $true
        }
        catch {}
        finally {
            if ($client) { $client.Close() }
        }
        Start-Sleep -Milliseconds 500
    }
    return $false
}


# --- Free the backend port ---------------------------------------------------

Write-Host "=== Killing processes on port $Port ===" -ForegroundColor Cyan
$portPids = (Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue).OwningProcess |
Sort-Object -Unique
foreach ($pidToKill in $portPids) {
Stop-Process -Id $pidToKill -Force -ErrorAction SilentlyContinue
Write-Host "Killed process $pidToKill on port $Port"
}
Comment on lines +69 to 74

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Calling Get-NetTCPConnection without specifying -State Listen will return all TCP connections, including active outbound connections where the local ephemeral port happens to match $Port. This could lead to accidentally killing unrelated processes (like web browsers or IDEs). Additionally, wrapping the result in @(...) and checking if it's not null prevents errors when no processes are listening.

$connections = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
if ($connections) {
    $portPids = @($connections.OwningProcess) | Sort-Object -Unique
    foreach ($pidToKill in $portPids) {
        Stop-Process -Id $pidToKill -Force -ErrorAction SilentlyContinue
        Write-Host "Killed process $pidToKill on port $Port"
    }
}


# --- Flutter clean + codegen -------------------------------------------------

Write-Host "=== Cleaning Flutter build ===" -ForegroundColor Cyan
flutter clean

Expand All @@ -27,77 +84,85 @@ flutter pub get
Write-Host "=== Regenerating code ===" -ForegroundColor Cyan
dart run build_runner build --delete-conflicting-outputs

# --- Backend build -----------------------------------------------------------

Write-Host "=== Building backend ===" -ForegroundColor Cyan
Set-Location backend
Push-Location backend
try {
if (-not (Test-Path ".env")) {
Write-Host "WARNING: backend/.env not found! Copying .env.example to .env..." -ForegroundColor Yellow
Copy-Item ".env.example" ".env"
Write-Host "Please make sure to fill in your database credentials in backend/.env if needed." -ForegroundColor Yellow
}

if (-not (Test-Path ".env")) {
Write-Host "WARNING: backend/.env not found! Copying .env.example to .env..." -ForegroundColor Yellow
Copy-Item ".env.example" ".env"
Write-Host "Please make sure to fill in your database credentials in backend/.env if needed." -ForegroundColor Yellow
dart pub get
dart pub global run dart_frog_cli:dart_frog build

Write-Host "=== Starting backend server ===" -ForegroundColor Cyan
# Set PORT for the child process (inherited from this session), then restore
# it so the variable doesn't leak. Avoids Start-Process -Environment, which
# requires PowerShell 7.4+ (Windows ships 5.1 by default).
$prevPort = $env:PORT
$env:PORT = "$Port"
try {
$serverProc = Start-Process dart `
-ArgumentList "build/bin/server.dart" `
-NoNewWindow -PassThru
}
finally {
$env:PORT = $prevPort
}
Write-Host "Backend server started (PID $($serverProc.Id))"
}
finally {
Pop-Location
}

dart pub global run dart_frog_cli:dart_frog build

Write-Host "=== Starting backend server ===" -ForegroundColor Cyan
$env:PORT = "8080"
Start-Process dart -ArgumentList "build/bin/server.dart" -NoNewWindow
Set-Location ..

Write-Host "=== Waiting for backend to start ===" -ForegroundColor Cyan
Start-Sleep -Seconds 3

$adbExe = "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe"
$deviceConnected = $false

if (Test-Path $adbExe) {
$devices = & $adbExe devices
$connectedDevices = $devices | Where-Object { $_ -match '\bdevice$' }
if ($connectedDevices) {
$deviceConnected = $true
Write-Host "=== Device already connected ===" -ForegroundColor Green
Write-Host "Skipping emulator launch."
}
if (-not (Wait-ForPort -PortNumber $Port)) {
Write-Host "Backend did not start listening on port $Port in time." -ForegroundColor Red
exit 1
}
Write-Host "Backend is up on port $Port." -ForegroundColor Green

# --- Ensure an Android device is available -----------------------------------

if (-not $deviceConnected) {
$targetDevice = Get-ConnectedAndroidDevice

if ($targetDevice) {
Write-Host "=== Device already connected ($targetDevice) ===" -ForegroundColor Green
Write-Host "Skipping emulator launch."
} else {
Write-Host "=== Starting Android emulator ===" -ForegroundColor Cyan
$emulatorExe = "$env:LOCALAPPDATA\Android\Sdk\emulator\emulator.exe"
if (Test-Path $emulatorExe) {
# You can change Pixel_10 to whatever your emulator AVD name is
Write-Host "Starting Pixel_10 emulator..."
Start-Process $emulatorExe -ArgumentList "-avd Pixel_10" -NoNewWindow

Write-Host "Starting $Avd emulator..."
Start-Process $emulatorExe -ArgumentList "-avd", $Avd -NoNewWindow

Write-Host "=== Waiting for emulator to boot ===" -ForegroundColor Cyan
if (Test-Path $adbExe) {
& $adbExe wait-for-device
Start-Sleep -Seconds 5
$targetDevice = Get-ConnectedAndroidDevice
}
} else {
Write-Host "Could not find emulator at $emulatorExe. Please ensure an emulator is running." -ForegroundColor Yellow
}
}

if (Test-Path $adbExe) {
# --- Port forwarding ---------------------------------------------------------

if ($targetDevice) {
Write-Host "=== Setting up port forwarding ===" -ForegroundColor Cyan
& $adbExe reverse tcp:8080 tcp:8080
& $adbExe -s $targetDevice reverse "tcp:$Port" "tcp:$Port"
} else {
Write-Host "Could not find adb at $adbExe. Skipping port forwarding." -ForegroundColor Yellow
Write-Host "No Android device detected. Skipping port forwarding." -ForegroundColor Yellow
}

Write-Host "=== Running Flutter app ===" -ForegroundColor Cyan

# Try to find a connected physical Android device ID
$targetDevice = $null
if (Test-Path $adbExe) {
$adbDevices = & $adbExe devices | Select-Object -Skip 1
$physicalDevice = $adbDevices | Where-Object { $_ -match '\bdevice$' } | Select-Object -First 1
if ($physicalDevice) {
$targetDevice = ($physicalDevice -split '\s+')[0]
Write-Host "Targeting device: $targetDevice" -ForegroundColor Green
}
}
# --- Run the app -------------------------------------------------------------

Write-Host "=== Running Flutter app ===" -ForegroundColor Cyan
if ($targetDevice) {
Write-Host "Targeting device: $targetDevice" -ForegroundColor Green
flutter run -d $targetDevice
} else {
flutter run
Expand Down