← Files ModRetro Chromatic PluginARCHIVED FILE

scripts/setup-windows.ps1

8.08 KB · Oct 2, 2026 · 00:37 UTC

↓ Download file

# A thin PowerShell entrypoint. The Node coordinator owns prerequisite checks
# and delegates installation to the existing supported setup scripts.
[CmdletBinding(PositionalBinding = $false)]
param(
    [string] $ToolchainRoot,
    [string] $PayloadRoot,
    [string] $MarketplaceRoot,
    [string] $MarketplaceName,
    [string] $Python,
    [switch] $WithDesktop,
    [switch] $SkipEmulator,
    [switch] $UseExisting,
    [switch] $ReplaceLink,
    [switch] $DryRun,
    [switch] $Json,
    [switch] $Help
)

$ErrorActionPreference = 'Stop'

function Resolve-GbSetupPhysicalPath {
    param([string] $Candidate, [int] $Depth = 0)
    if ($Depth -gt 32) { throw 'Too many filesystem links while locating Node.js.' }
    $gbSetupProviderPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Candidate)
    $gbSetupFullPath = [System.IO.Path]::GetFullPath($gbSetupProviderPath)
    $gbSetupRoot = [System.IO.Path]::GetPathRoot($gbSetupFullPath)
    $gbSetupParts = @($gbSetupFullPath.Substring($gbSetupRoot.Length) -split '[\\/]' | Where-Object { $_ -ne '' })
    $gbSetupCurrent = $gbSetupRoot
    for ($gbSetupIndex = 0; $gbSetupIndex -lt $gbSetupParts.Length; $gbSetupIndex++) {
        $gbSetupCurrent = Join-Path $gbSetupCurrent $gbSetupParts[$gbSetupIndex]
        $gbSetupItem = Get-Item -LiteralPath $gbSetupCurrent -Force
        if (($gbSetupItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) { continue }
        $gbSetupTarget = @($gbSetupItem.Target)[0]
        if ([string]::IsNullOrWhiteSpace($gbSetupTarget)) { throw 'An executable path uses an unsupported reparse point.' }
        if ($gbSetupTarget.StartsWith('\??\')) { $gbSetupTarget = $gbSetupTarget.Substring(4) }
        if ($gbSetupTarget.StartsWith('\\?\UNC\', [System.StringComparison]::OrdinalIgnoreCase)) {
            $gbSetupTarget = '\\' + $gbSetupTarget.Substring(8)
        } elseif ($gbSetupTarget.StartsWith('\\?\')) {
            $gbSetupTarget = $gbSetupTarget.Substring(4)
        }
        if (-not [System.IO.Path]::IsPathRooted($gbSetupTarget)) {
            $gbSetupTarget = Join-Path ([System.IO.Path]::GetDirectoryName($gbSetupCurrent)) $gbSetupTarget
        }
        for ($gbSetupRest = $gbSetupIndex + 1; $gbSetupRest -lt $gbSetupParts.Length; $gbSetupRest++) {
            $gbSetupTarget = Join-Path $gbSetupTarget $gbSetupParts[$gbSetupRest]
        }
        return Resolve-GbSetupPhysicalPath -Candidate $gbSetupTarget -Depth ($Depth + 1)
    }
    return $gbSetupCurrent
}

function Test-GbSetupWithinRoot {
    param([string] $Root, [string] $Candidate)
    $gbSetupPrefix = $Root.TrimEnd('\', '/') + [System.IO.Path]::DirectorySeparatorChar
    return $Candidate.Equals($Root, [System.StringComparison]::OrdinalIgnoreCase) -or
        $Candidate.StartsWith($gbSetupPrefix, [System.StringComparison]::OrdinalIgnoreCase)
}

if ($env:OS -ne 'Windows_NT') {
    throw 'This entrypoint supports Windows x64. Use the existing npm setup:compiler and setup:emulator commands on macOS/Linux.'
}
if ((Get-Location).Provider.Name -ne 'FileSystem') {
    throw 'Run ModRetro Chromatic setup from a filesystem directory in PowerShell.'
}

$gbSetupSource = Resolve-GbSetupPhysicalPath -Candidate (Join-Path $PSScriptRoot '..')
$gbSetupWorkingDirectory = Resolve-GbSetupPhysicalPath -Candidate (Get-Location).ProviderPath
$gbSetupRejectedRoots = @($gbSetupSource, $gbSetupWorkingDirectory)
if (-not [string]::IsNullOrWhiteSpace($env:GB_STUDIO_PROJECT_ROOT)) {
    try { $gbSetupRejectedRoots += Resolve-GbSetupPhysicalPath -Candidate $env:GB_STUDIO_PROJECT_ROOT } catch { }
}
$gbSetupToolchain = $gbSetupSource
if (-not [string]::IsNullOrWhiteSpace($ToolchainRoot)) {
    $gbSetupToolchain = Resolve-GbSetupPhysicalPath -Candidate $ToolchainRoot
} elseif (-not [string]::IsNullOrWhiteSpace($env:GB_STUDIO_TOOLCHAIN_ROOT)) {
    $gbSetupToolchain = Resolve-GbSetupPhysicalPath -Candidate $env:GB_STUDIO_TOOLCHAIN_ROOT
}
$gbSetupRejectedRoots += $gbSetupToolchain
$gbSetupLocal = Join-Path $gbSetupToolchain '.local'
$gbSetupLocalEntries = @(Get-ChildItem -LiteralPath $gbSetupToolchain -Force | Where-Object { $_.Name -ieq '.local' })
if ($gbSetupLocalEntries.Length -gt 1) { throw 'The game toolchain has ambiguous .local directory entries.' }
if ($gbSetupLocalEntries.Length -eq 1) {
    $gbSetupPhysicalLocal = Resolve-GbSetupPhysicalPath -Candidate $gbSetupLocal
    if (-not [System.IO.Path]::GetFileName($gbSetupPhysicalLocal).Equals('.local', [System.StringComparison]::OrdinalIgnoreCase)) {
        throw 'A linked game toolchain must resolve to an actual .local directory.'
    }
    $gbSetupLocalItem = Get-Item -LiteralPath $gbSetupPhysicalLocal -Force
    if (-not $gbSetupLocalItem.PSIsContainer) { throw 'The toolchain .local path must be a directory.' }
    $gbSetupRejectedRoots += $gbSetupPhysicalLocal
    $gbSetupRejectedRoots += [System.IO.Path]::GetDirectoryName($gbSetupPhysicalLocal)
}
# Native processes do not always share PowerShell's runspace location. Forward
# explicit absolute selections instead of relying on a process-wide cwd.
$ToolchainRoot = $gbSetupToolchain
if (-not [string]::IsNullOrWhiteSpace($PayloadRoot)) {
    $PayloadRoot = [System.IO.Path]::GetFullPath($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PayloadRoot))
}
if (-not [string]::IsNullOrWhiteSpace($MarketplaceRoot)) {
    $MarketplaceRoot = [System.IO.Path]::GetFullPath($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($MarketplaceRoot))
}

$gbSetupNode = $null
foreach ($gbSetupRawDirectory in ($env:PATH -split ';')) {
    $gbSetupDirectory = $gbSetupRawDirectory.Trim().Trim('"')
    if ([string]::IsNullOrWhiteSpace($gbSetupDirectory) -or -not [System.IO.Path]::IsPathRooted($gbSetupDirectory)) { continue }
    try {
        $gbSetupCandidate = Resolve-GbSetupPhysicalPath -Candidate (Join-Path $gbSetupDirectory 'node.exe')
        $gbSetupNodeItem = Get-Item -LiteralPath $gbSetupCandidate -Force
        if ($gbSetupNodeItem.PSIsContainer -or (($gbSetupNodeItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0)) { continue }
        $gbSetupRejected = $false
        foreach ($gbSetupRoot in $gbSetupRejectedRoots) {
            if (Test-GbSetupWithinRoot -Root $gbSetupRoot -Candidate $gbSetupCandidate) { $gbSetupRejected = $true; break }
        }
        if ($gbSetupRejected) { continue }
        $gbSetupNode = $gbSetupCandidate
        break
    } catch {
        # Never substitute node.cmd, an alias, or a checkout-controlled executable.
    }
}
if ($null -eq $gbSetupNode) {
    throw 'Install a current supported Node.js 22 or 24 LTS distribution with npm, reopen PowerShell, and rerun setup. A trusted node.exe on an absolute PATH entry outside this checkout is required.'
}

$gbSetupArguments = @()
foreach ($gbSetupOption in @(
    @('--toolchain-root', $ToolchainRoot),
    @('--payload-root', $PayloadRoot),
    @('--marketplace-root', $MarketplaceRoot),
    @('--marketplace-name', $MarketplaceName),
    @('--python', $Python)
)) {
    if (-not [string]::IsNullOrWhiteSpace($gbSetupOption[1])) { $gbSetupArguments += $gbSetupOption }
}
foreach ($gbSetupFlag in @(
    @('--with-desktop', $WithDesktop),
    @('--skip-emulator', $SkipEmulator),
    @('--use-existing', $UseExisting),
    @('--replace-link', $ReplaceLink),
    @('--dry-run', $DryRun),
    @('--json', $Json),
    @('--help', $Help)
)) {
    if ($gbSetupFlag[1]) { $gbSetupArguments += $gbSetupFlag[0] }
}

# These affect Node before our JavaScript can sanitize subprocess environments.
$gbSetupSavedNodeOptions = [Environment]::GetEnvironmentVariable('NODE_OPTIONS', 'Process')
$gbSetupSavedNodePath = [Environment]::GetEnvironmentVariable('NODE_PATH', 'Process')
$gbSetupExitCode = 1
try {
    [Environment]::SetEnvironmentVariable('NODE_OPTIONS', $null, 'Process')
    [Environment]::SetEnvironmentVariable('NODE_PATH', $null, 'Process')
    & $gbSetupNode (Join-Path $PSScriptRoot 'setup-windows.mjs') @gbSetupArguments
    $gbSetupExitCode = $LASTEXITCODE
} finally {
    [Environment]::SetEnvironmentVariable('NODE_OPTIONS', $gbSetupSavedNodeOptions, 'Process')
    [Environment]::SetEnvironmentVariable('NODE_PATH', $gbSetupSavedNodePath, 'Process')
}
exit $gbSetupExitCode

SHA-256: a89acf72251b3532237d00022d7b574d2a0fe7ea336277cecfa53bb31f96bd25