| 1 | # Installs the Diffinery CLI on Windows: |
| 2 | # |
| 3 | # powershell -c "irm https://diffinery.com/install.ps1 | iex" |
| 4 | # |
| 5 | # The CLI goes in %USERPROFILE%\.diffinery (set DIFFINERY_INSTALL to change that), and its bin |
| 6 | # folder is added to your user PATH (DIFFINERY_NO_MODIFY_PATH=1 skips that). It runs on Node.js 22 |
| 7 | # or later: the one on your PATH when it's new enough, otherwise a private copy of the Node.js LTS |
| 8 | # from nodejs.org, checked against its published SHA-256, in .diffinery\node. |
| 9 | # Run it again, or `diffinery upgrade`, to update. Served by Diffinery from apps/cli/install/. |
| 10 | # `irm | iex` runs this in your session: the block keeps its settings and variables out of it. |
| 11 | & { |
| 12 | $ErrorActionPreference = 'Stop' |
| 13 | # Invoke-WebRequest is many times faster without its progress bar. |
| 14 | $ProgressPreference = 'SilentlyContinue' |
| 15 | [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 |
| 16 | |
| 17 | $Server = if ($env:DIFFINERY_URL) { $env:DIFFINERY_URL } else { '__DIFFINERY_URL__' } |
| 18 | if ($Server.StartsWith('__')) { $Server = 'https://diffinery.com' } |
| 19 | $Server = $Server.TrimEnd('/') |
| 20 | $Root = if ($env:DIFFINERY_INSTALL) { $env:DIFFINERY_INSTALL } else { Join-Path $HOME '.diffinery' } |
| 21 | $Bin = Join-Path $Root 'bin' |
| 22 | $Lib = Join-Path $Root 'lib' |
| 23 | $Private = Join-Path $Root 'node\node.exe' |
| 24 | $NodeLine = 24 |
| 25 | |
| 26 | function Test-Node([string] $Exe) { |
| 27 | try { |
| 28 | & $Exe -e "process.exit(Number(process.versions.node.split('.')[0]) >= 22 ? 0 : 1)" *> $null |
| 29 | return $LASTEXITCODE -eq 0 |
| 30 | } catch { |
| 31 | return $false |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | # 1. Node.js for the CLI to run on. |
| 36 | $OwnNode = $false |
| 37 | $SystemNode = Get-Command node -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 |
| 38 | if ((Test-Path $Private) -and (Test-Node $Private)) { |
| 39 | $OwnNode = $true |
| 40 | } elseif (-not ($SystemNode -and (Test-Node $SystemNode.Source))) { |
| 41 | $Arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64' -or $env:PROCESSOR_ARCHITEW6432 -eq 'ARM64') { 'arm64' } else { 'x64' } |
| 42 | Write-Host "Installing Node.js $NodeLine LTS for the CLI, from nodejs.org..." |
| 43 | $Dist = "https://nodejs.org/dist/latest-v$NodeLine.x" |
| 44 | $Sums = [string] (Invoke-WebRequest -UseBasicParsing "$Dist/SHASUMS256.txt").Content |
| 45 | $Line = $Sums -split "`n" | Where-Object { $_ -match "^[0-9a-f]{64} node-v[0-9.]+-win-$Arch\.zip$" } | Select-Object -First 1 |
| 46 | if (-not $Line) { throw "nodejs.org has no Node.js build for win-$Arch" } |
| 47 | $Want, $File = $Line.Trim() -split '\s+' |
| 48 | $Tmp = Join-Path ([IO.Path]::GetTempPath()) ("diffinery-" + [guid]::NewGuid().ToString('n')) |
| 49 | New-Item -ItemType Directory -Path $Tmp | Out-Null |
| 50 | try { |
| 51 | $Zip = Join-Path $Tmp $File |
| 52 | Invoke-WebRequest -UseBasicParsing "$Dist/$File" -OutFile $Zip |
| 53 | if ((Get-FileHash -Algorithm SHA256 $Zip).Hash.ToLower() -ne $Want) { throw "$File doesn't match its published SHA-256, so it wasn't installed" } |
| 54 | Expand-Archive -Path $Zip -DestinationPath $Tmp |
| 55 | $Dest = Join-Path $Root 'node' |
| 56 | New-Item -ItemType Directory -Force -Path $Root | Out-Null |
| 57 | if (Test-Path $Dest) { Remove-Item -Recurse -Force $Dest } |
| 58 | Move-Item (Join-Path $Tmp ($File -replace '\.zip$', '')) $Dest |
| 59 | } finally { |
| 60 | Remove-Item -Recurse -Force $Tmp -ErrorAction SilentlyContinue |
| 61 | } |
| 62 | $OwnNode = $true |
| 63 | } |
| 64 | |
| 65 | # 2. The CLI itself: one JavaScript file. |
| 66 | New-Item -ItemType Directory -Force -Path $Bin, $Lib | Out-Null |
| 67 | $Cli = Join-Path $Lib 'diffinery.mjs' |
| 68 | $Download = "$Cli.download" |
| 69 | Invoke-WebRequest -UseBasicParsing "$Server/-/cli/diffinery.mjs" -OutFile $Download |
| 70 | if (-not ((Get-Content $Download -TotalCount 1) -like '#!/usr/bin/env node*')) { |
| 71 | Remove-Item -Force $Download |
| 72 | throw "$Server/-/cli/diffinery.mjs isn't the CLI" |
| 73 | } |
| 74 | Move-Item -Force $Download $Cli |
| 75 | |
| 76 | # 3. The diffinery command: the private Node.js when there is one, else the node on PATH. |
| 77 | $Cmd = @( |
| 78 | '@echo off', |
| 79 | "rem The Diffinery CLI, installed from $Server/install.ps1. Update it with: diffinery upgrade", |
| 80 | 'setlocal', |
| 81 | 'set "DIFFINERY_NODE=node"', |
| 82 | 'if exist "%~dp0..\node\node.exe" set "DIFFINERY_NODE=%~dp0..\node\node.exe"', |
| 83 | '"%DIFFINERY_NODE%" "%~dp0..\lib\diffinery.mjs" %*' |
| 84 | ) -join "`r`n" |
| 85 | Set-Content -Path (Join-Path $Bin 'diffinery.cmd') -Value $Cmd -Encoding ASCII |
| 86 | $Version = & (Join-Path $Bin 'diffinery.cmd') --version |
| 87 | if ($LASTEXITCODE -ne 0) { throw "the CLI was installed in $Root but doesn't start; run $Bin\diffinery.cmd --version to see why" } |
| 88 | |
| 89 | # 4. PATH, for new terminals and this one. |
| 90 | $Note = $null |
| 91 | $UserPath = [Environment]::GetEnvironmentVariable('Path', 'User') |
| 92 | if (-not (($UserPath -split ';') -contains $Bin)) { |
| 93 | if ($env:DIFFINERY_NO_MODIFY_PATH) { |
| 94 | $Note = "Add $Bin to your PATH to run diffinery." |
| 95 | } else { |
| 96 | [Environment]::SetEnvironmentVariable('Path', ((@($UserPath, $Bin) | Where-Object { $_ }) -join ';'), 'User') |
| 97 | $Note = "Added $Bin to your user PATH. Open a new terminal to use it everywhere." |
| 98 | } |
| 99 | } |
| 100 | if (-not (($env:Path -split ';') -contains $Bin)) { $env:Path = "$Bin;$env:Path" } |
| 101 | |
| 102 | Write-Host "$([char]0x2713) $Version installed to $Bin\diffinery.cmd$(if ($OwnNode) { ' (with its own Node.js)' })" -ForegroundColor Green |
| 103 | if ($Note) { Write-Host $Note } |
| 104 | $Login = if ($Server -eq 'https://diffinery.com') { 'diffinery login' } else { "diffinery login --url $Server" } |
| 105 | Write-Host "" |
| 106 | Write-Host "Next, sign in through your browser:" |
| 107 | Write-Host " $Login" |
| 108 | } |