Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

108 lines · 5.4 KBRaw History
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}