@join-pantheon/cli 0.31.2-rc.1 → 0.32.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/claude-confine.js +12 -2
- package/dist/cli.js +56 -17
- package/dist/doctor.js +64 -6
- package/dist/helpers-build.js +69 -1
- package/dist/install.js +25 -11
- package/dist/installer.js +204 -0
- package/dist/machine.js +15 -1
- package/dist/managed.js +238 -0
- package/dist/mcp-supervisor.js +2 -1
- package/dist/measure.js +2 -1
- package/dist/notifier.js +7 -17
- package/dist/notify.js +7 -1
- package/dist/relay.js +67 -6
- package/dist/service.js +14 -6
- package/dist/tray.js +5 -10
- package/dist/uninstall.js +194 -0
- package/dist/upgrade.js +44 -5
- package/dist/which.js +11 -3
- package/dist/worker-policy.js +2 -0
- package/dist/worker.js +30 -7
- package/helper/macos/prebuilt/manifest.json +29 -0
- package/helper/macos/prebuilt/pantheon-hud +0 -0
- package/helper/macos/prebuilt/pantheon-tray +0 -0
- package/installer/install.ps1 +547 -0
- package/installer/install.sh +612 -0
- package/package.json +8 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"built_at": "2026-10-03T10:33:33.279Z",
|
|
4
|
+
"swiftc": "Apple Swift version 6.3.3 (swiftlang-6.3.3.1.3 clang-2100.1.1.101)",
|
|
5
|
+
"helpers": {
|
|
6
|
+
"pantheon-tray": {
|
|
7
|
+
"source": "menubar.swift",
|
|
8
|
+
"source_sha256": "f079629099a81f9b679ef4133d897ceb09025f675099c5f941951604df3531fa",
|
|
9
|
+
"sha256": "cc081120555f528c83b38951663d37e8394e3a3e119a19b0bb3b2a737f4d617f",
|
|
10
|
+
"archs": [
|
|
11
|
+
"arm64",
|
|
12
|
+
"x86_64"
|
|
13
|
+
],
|
|
14
|
+
"target": "macos11",
|
|
15
|
+
"version": "2"
|
|
16
|
+
},
|
|
17
|
+
"pantheon-hud": {
|
|
18
|
+
"source": "main.swift",
|
|
19
|
+
"source_sha256": "bcf55a19b658d1f09a9d117c76030393ab08a2569062dd7413f884ca05f3a0eb",
|
|
20
|
+
"sha256": "0414b397a533940e3a5c324add6250e1ed71919e67e462d11dcb2a1d0c2f9b41",
|
|
21
|
+
"archs": [
|
|
22
|
+
"arm64",
|
|
23
|
+
"x86_64"
|
|
24
|
+
],
|
|
25
|
+
"target": "macos11",
|
|
26
|
+
"version": "4"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Pantheon installer for Windows (Windows PowerShell 5.1 and PowerShell 7).
|
|
2
|
+
#
|
|
3
|
+
# In PowerShell:
|
|
4
|
+
# irm https://relay.joinpantheon.network/install.ps1 | iex (install only)
|
|
5
|
+
# $env:PANTHEON_ARGS = "join '<invite link>'"; irm https://relay.joinpantheon.network/install.ps1 | iex
|
|
6
|
+
# & ([scriptblock]::Create((irm https://relay.joinpantheon.network/install.ps1))) join '<invite link>'
|
|
7
|
+
# (not under Constrained Language Mode, which refuses [scriptblock]::Create
|
|
8
|
+
# before this script can explain: there, use the `irm | iex` forms)
|
|
9
|
+
#
|
|
10
|
+
# In Command Prompt (cmd.exe), as one line:
|
|
11
|
+
# powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:PANTHEON_ARGS='join <invite link>'; irm https://relay.joinpantheon.network/install.ps1 | iex"
|
|
12
|
+
#
|
|
13
|
+
# One line for either window (what `pantheon invite` prints): PowerShell would
|
|
14
|
+
# expand the $env: above inside the outer double quotes before the inner
|
|
15
|
+
# PowerShell runs, so this sets the variable without a $, and with a cmdlet
|
|
16
|
+
# that Constrained Language Mode still allows:
|
|
17
|
+
# powershell -NoProfile -ExecutionPolicy Bypass -Command "Set-Item Env:PANTHEON_ARGS 'join <invite link>'; irm https://relay.joinpantheon.network/install.ps1 | iex"
|
|
18
|
+
#
|
|
19
|
+
# `irm | iex` cannot pass arguments, so the arguments for pantheon travel in
|
|
20
|
+
# $env:PANTHEON_ARGS, which reads the same from PowerShell and from cmd.exe.
|
|
21
|
+
# The scriptblock form passes them as real arguments instead. When both are
|
|
22
|
+
# given, the real arguments win. PANTHEON_ARGS is cleared once read, so a later
|
|
23
|
+
# plain install in the same window does not repeat a `join`.
|
|
24
|
+
#
|
|
25
|
+
# What it does, and nothing else:
|
|
26
|
+
# 1. downloads one pinned Node release (the win-x64 or win-arm64 zip) from
|
|
27
|
+
# nodejs.org, checks its sha256 against the one pinned in this file, and
|
|
28
|
+
# unpacks it into %LOCALAPPDATA%\Pantheon\runtime\node. That Node is
|
|
29
|
+
# Pantheon's own; a Node you already have is not touched.
|
|
30
|
+
# 2. installs the Pantheon CLI with THAT Node's npm into that same folder.
|
|
31
|
+
# No administrator rights, no execution-policy change, no WSL.
|
|
32
|
+
# 3. writes %LOCALAPPDATA%\Pantheon\bin\pantheon.cmd, which runs that Node.
|
|
33
|
+
# 4. puts %LOCALAPPDATA%\Pantheon\bin first on your user PATH, and on this
|
|
34
|
+
# window's PATH.
|
|
35
|
+
# 5. runs `pantheon <the arguments you gave>`.
|
|
36
|
+
#
|
|
37
|
+
# Running it again is safe: whatever is already right is left as it is. Two
|
|
38
|
+
# copies at once are safe too: the second waits for the first.
|
|
39
|
+
# The relay that serves this file fills in the Pantheon version below.
|
|
40
|
+
|
|
41
|
+
# Constrained Language Mode (an IT policy: AppLocker or WDAC) refuses the .NET
|
|
42
|
+
# calls this installer needs. Said plainly, before the first one fails with an
|
|
43
|
+
# error about "core types". `throw` makes `powershell -Command ... | iex` exit 1
|
|
44
|
+
# (Write-Error inside iex does not), and leaves an open window open.
|
|
45
|
+
if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') {
|
|
46
|
+
Write-Host 'pantheon install failed: PowerShell on this computer runs in Constrained Language Mode (set by your IT), which this installer cannot run in. Ask IT to allow it, or if you have Node.js 22.13 or newer: npm install -g @join-pantheon/cli' -ForegroundColor Red
|
|
47
|
+
Remove-Item Env:PANTHEON_ARGS -ErrorAction SilentlyContinue
|
|
48
|
+
throw 'Pantheon was not installed (Constrained Language Mode).'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# The arguments of the scriptblock form; empty under `irm | iex`.
|
|
52
|
+
$PantheonInstallArgs = @()
|
|
53
|
+
try { if ($null -ne $args) { $PantheonInstallArgs = @($args) } } catch { $PantheonInstallArgs = @() }
|
|
54
|
+
|
|
55
|
+
# The result travels in $State rather than as output: anything a function
|
|
56
|
+
# returns is captured from its output stream, and pantheon's own output must
|
|
57
|
+
# reach the console untouched.
|
|
58
|
+
function Install-Pantheon {
|
|
59
|
+
param([string[]]$Arguments = @(), [hashtable]$State)
|
|
60
|
+
|
|
61
|
+
$PantheonVersion = '__PANTHEON_VERSION__'
|
|
62
|
+
$NodeVersion = '22.23.3'
|
|
63
|
+
# The sha256 of each Node zip this script may install, from nodejs.org's
|
|
64
|
+
# SHASUMS256.txt for $NodeVersion. A download is checked against THESE, never
|
|
65
|
+
# against a sums file fetched beside it, which a mirror could serve to match
|
|
66
|
+
# whatever it sends. Bumping $NodeVersion means new pins: scripts/node-pins.mjs.
|
|
67
|
+
$NodePins = @{
|
|
68
|
+
'node-v22.23.3-win-arm64.zip' = '33dad22e4cef5ee8f9fbb1b0d037fdacd0e56d12a4580f0d63f68b894deab535'
|
|
69
|
+
'node-v22.23.3-win-x64.zip' = '2b0ff57b049cda1bbcea2240eec20467018713c1efe1f7360c2681859b90ed71'
|
|
70
|
+
}
|
|
71
|
+
# Another copy of nodejs.org/dist (a company mirror), if nodejs.org is blocked.
|
|
72
|
+
$NodeDist = if ($env:PANTHEON_NODE_MIRROR) { $env:PANTHEON_NODE_MIRROR.TrimEnd('/') } else { 'https://nodejs.org/dist' }
|
|
73
|
+
$Site = 'https://relay.joinpantheon.network'
|
|
74
|
+
|
|
75
|
+
$ErrorActionPreference = 'Stop'
|
|
76
|
+
# Invoke-WebRequest's progress bar makes Windows PowerShell 5.1 downloads many times slower.
|
|
77
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
78
|
+
# Every failure is one plain line: what failed, and what to do about it.
|
|
79
|
+
function Fail([string]$Message) { throw "PANTHEON_FAIL:$Message" }
|
|
80
|
+
function Say([string]$Message) { Write-Host $Message }
|
|
81
|
+
function Note([string]$Message) { Write-Host "note: $Message" -ForegroundColor Yellow }
|
|
82
|
+
|
|
83
|
+
# One argument string as the C runtime (and so node.exe) splits it back up.
|
|
84
|
+
function ConvertTo-ArgLine([string[]]$Items) {
|
|
85
|
+
($Items | ForEach-Object {
|
|
86
|
+
if ($_ -eq '') { '""' }
|
|
87
|
+
elseif ($_ -notmatch '[\s"]') { $_ }
|
|
88
|
+
else { '"' + (($_ -replace '(\\*)"', '$1$1\"') -replace '(\\+)$', '$1$1') + '"' }
|
|
89
|
+
}) -join ' '
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Run a program with its output into a log file and nothing on screen. A
|
|
93
|
+
# Process object, because in Windows PowerShell 5.1 redirecting a native
|
|
94
|
+
# command's stderr under ErrorActionPreference=Stop turns its first warning
|
|
95
|
+
# line into a terminating error.
|
|
96
|
+
function Invoke-Logged([string]$Exe, [string[]]$Items, [string]$LogFile) {
|
|
97
|
+
$r = Invoke-Quiet $Exe $Items
|
|
98
|
+
[IO.File]::WriteAllText($LogFile, $r.out + $r.err)
|
|
99
|
+
return $r.code
|
|
100
|
+
}
|
|
101
|
+
# The same, with the output handed back instead: @{ code; out; err }.
|
|
102
|
+
function Invoke-Quiet([string]$Exe, [string[]]$Items) {
|
|
103
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
104
|
+
$psi.FileName = $Exe
|
|
105
|
+
$psi.Arguments = ConvertTo-ArgLine $Items
|
|
106
|
+
$psi.UseShellExecute = $false
|
|
107
|
+
$psi.CreateNoWindow = $true
|
|
108
|
+
$psi.RedirectStandardInput = $true
|
|
109
|
+
$psi.RedirectStandardOutput = $true
|
|
110
|
+
$psi.RedirectStandardError = $true
|
|
111
|
+
$p = [System.Diagnostics.Process]::Start($psi)
|
|
112
|
+
$p.StandardInput.Close()
|
|
113
|
+
$out = $p.StandardOutput.ReadToEndAsync()
|
|
114
|
+
$err = $p.StandardError.ReadToEndAsync()
|
|
115
|
+
$p.WaitForExit()
|
|
116
|
+
return @{ code = $p.ExitCode; out = $out.Result; err = $err.Result }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
if ($PantheonVersion -like '*__*') {
|
|
121
|
+
Fail "this copy of install.ps1 has no version filled in. Use the one the relay serves: irm $Site/install.ps1 | iex"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# ---- what this machine is ----------------------------------------------
|
|
125
|
+
|
|
126
|
+
$State.step = 'checking this machine'
|
|
127
|
+
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
|
|
128
|
+
Fail "this installer is for Windows. On macOS or Linux: curl -fsSL $Site/install.sh | sh"
|
|
129
|
+
}
|
|
130
|
+
if ([Environment]::OSVersion.Version.Major -lt 10) {
|
|
131
|
+
Fail "Node $NodeVersion needs Windows 10, Windows Server 2016 or newer."
|
|
132
|
+
}
|
|
133
|
+
$arch = $null
|
|
134
|
+
try { $arch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() } catch { $arch = $null }
|
|
135
|
+
if (-not $arch) { $arch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE } }
|
|
136
|
+
switch -regex ($arch) {
|
|
137
|
+
'^(X64|AMD64)$' { $nodeArch = 'x64'; break }
|
|
138
|
+
'^ARM64$' { $nodeArch = 'arm64'; break }
|
|
139
|
+
default { Fail "this processor ($arch) has no Node $NodeVersion build for Windows. Pantheon runs on x64 and arm64." }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# A cloud workspace can install Pantheon, but what it installs goes away with
|
|
143
|
+
# the session. The same names src/host-env.ts looks for.
|
|
144
|
+
$cloud = @('CODESPACES', 'CLOUDENV_ENVIRONMENT_ID', 'REMOTE_CONTAINERS', 'DEVCONTAINER', 'GITPOD_WORKSPACE_ID', 'CLOUD_SHELL', 'REPL_ID', 'KUBERNETES_SERVICE_HOST') |
|
|
145
|
+
Where-Object { [Environment]::GetEnvironmentVariable($_) }
|
|
146
|
+
if ($cloud) {
|
|
147
|
+
Note "this looks like a cloud workspace, not the computer you work on (`$$($cloud -join ', $') set). Pantheon will install, but it goes away with this session."
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# Windows PowerShell 5.1 may still offer only TLS 1.0 to nodejs.org.
|
|
151
|
+
try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { }
|
|
152
|
+
|
|
153
|
+
# Both spellings, so Invoke-WebRequest, npm and Pantheon see the same proxy.
|
|
154
|
+
foreach ($pair in @(@('HTTPS_PROXY', 'https_proxy'), @('HTTP_PROXY', 'http_proxy'), @('NO_PROXY', 'no_proxy'))) {
|
|
155
|
+
$v = [Environment]::GetEnvironmentVariable($pair[0]); if (-not $v) { $v = [Environment]::GetEnvironmentVariable($pair[1]) }
|
|
156
|
+
if ($v) { [Environment]::SetEnvironmentVariable($pair[0], $v, 'Process'); [Environment]::SetEnvironmentVariable($pair[1], $v, 'Process') }
|
|
157
|
+
}
|
|
158
|
+
$proxy = $env:HTTPS_PROXY
|
|
159
|
+
|
|
160
|
+
$localAppData = [Environment]::GetFolderPath('LocalApplicationData')
|
|
161
|
+
if (-not $localAppData) { $localAppData = $env:LOCALAPPDATA }
|
|
162
|
+
if (-not $localAppData) { Fail 'LOCALAPPDATA is not set, so there is nowhere to install. Run this from your own Windows account.' }
|
|
163
|
+
$root = Join-Path $localAppData 'Pantheon'
|
|
164
|
+
$runtime = Join-Path $root 'runtime'
|
|
165
|
+
$nodeDir = Join-Path $runtime 'node'
|
|
166
|
+
$nodeExe = Join-Path $nodeDir 'node.exe'
|
|
167
|
+
$npmCli = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js'
|
|
168
|
+
$pkgDir = Join-Path $nodeDir 'node_modules\@join-pantheon\cli'
|
|
169
|
+
$entry = Join-Path $pkgDir 'bin\pantheon.js'
|
|
170
|
+
$binDir = Join-Path $root 'bin'
|
|
171
|
+
$launcher = Join-Path $binDir 'pantheon.cmd'
|
|
172
|
+
$log = Join-Path $root 'install.log'
|
|
173
|
+
|
|
174
|
+
$State.step = "creating $root"
|
|
175
|
+
try { New-Item -ItemType Directory -Force -Path $runtime, $binDir | Out-Null }
|
|
176
|
+
catch { Fail "could not create $runtime ($($_.Exception.Message)). Check that the folder is yours and the disk has space, then run this again." }
|
|
177
|
+
|
|
178
|
+
# ---- one installer at a time -------------------------------------------
|
|
179
|
+
|
|
180
|
+
# Two installers at once (a second window, a line pasted twice) would swap
|
|
181
|
+
# the runtime under each other. The lock is a file this process holds open
|
|
182
|
+
# with no sharing; Windows lets go of it when the process ends, however it
|
|
183
|
+
# ends (Ctrl-C, a closed window, Task Manager), so it is never stale. The
|
|
184
|
+
# second installer waits for the first, then finds everything in place.
|
|
185
|
+
$State.step = 'waiting for another Pantheon install'
|
|
186
|
+
$lockFile = Join-Path $runtime 'install.lock'
|
|
187
|
+
$waited = 0
|
|
188
|
+
while (-not $State.lock) {
|
|
189
|
+
try { $State.lock = [IO.File]::Open($lockFile, [IO.FileMode]::OpenOrCreate, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None) }
|
|
190
|
+
catch {
|
|
191
|
+
$ex = $_.Exception
|
|
192
|
+
while ($ex.InnerException) { $ex = $ex.InnerException }
|
|
193
|
+
if ($ex -isnot [IO.IOException]) { throw }
|
|
194
|
+
if ($waited -eq 0) { Say 'Another Pantheon install is running; waiting for it to finish...' }
|
|
195
|
+
if ($waited -ge 600) { Fail 'another Pantheon install has been running for over 10 minutes. Let it finish or close its window, then run this again.' }
|
|
196
|
+
Start-Sleep -Seconds 1
|
|
197
|
+
$waited++
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
# What an installer stopped by a power cut left behind (see $tmp below).
|
|
201
|
+
Get-ChildItem -LiteralPath $runtime -Directory -Force -ErrorAction SilentlyContinue |
|
|
202
|
+
Where-Object { $_.Name -match '^t[0-9a-f]{6}$' } |
|
|
203
|
+
ForEach-Object { Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue }
|
|
204
|
+
|
|
205
|
+
function Get-Download([string]$Url, [string]$OutFile) {
|
|
206
|
+
$params = @{ Uri = $Url; OutFile = $OutFile; UseBasicParsing = $true }
|
|
207
|
+
if ($proxy) { $params.Proxy = $proxy; $params.ProxyUseDefaultCredentials = $true }
|
|
208
|
+
$last = ''
|
|
209
|
+
for ($i = 1; $i -le 3; $i++) {
|
|
210
|
+
try { Invoke-WebRequest @params; return } catch { $last = $_.Exception.Message; Start-Sleep -Seconds 2 }
|
|
211
|
+
}
|
|
212
|
+
Fail "could not download $Url ($last). Check your internet connection; behind a proxy set HTTPS_PROXY; then run this again."
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# ---- 1. Pantheon's own Node --------------------------------------------
|
|
216
|
+
|
|
217
|
+
$haveNode = ''
|
|
218
|
+
if (Test-Path -LiteralPath $nodeExe) { try { $haveNode = (& $nodeExe --version) 2>$null } catch { $haveNode = '' } }
|
|
219
|
+
if ($haveNode -eq "v$NodeVersion") {
|
|
220
|
+
Say "Node v$NodeVersion for Pantheon is already in $nodeDir."
|
|
221
|
+
} else {
|
|
222
|
+
$zipName = "node-v$NodeVersion-win-$nodeArch.zip"
|
|
223
|
+
# Short names on purpose: npm's deepest file plus this folder must stay
|
|
224
|
+
# under Windows PowerShell's 260-character path limit.
|
|
225
|
+
$tmp = Join-Path $runtime ('t' + [Guid]::NewGuid().ToString('N').Substring(0, 6))
|
|
226
|
+
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
|
|
227
|
+
$aside = $null
|
|
228
|
+
try {
|
|
229
|
+
$State.step = "downloading Node v$NodeVersion"
|
|
230
|
+
Say "Downloading Node v$NodeVersion for Pantheon (win-$nodeArch)..."
|
|
231
|
+
$zip = Join-Path $tmp $zipName
|
|
232
|
+
$want = $NodePins[$zipName]
|
|
233
|
+
if (-not $want) { Fail "this installer has no checksum for $zipName, so it cannot install Node here. Install Node 22.13 or newer yourself, then: npm install -g @join-pantheon/cli@$PantheonVersion" }
|
|
234
|
+
Get-Download "$NodeDist/v$NodeVersion/$zipName" $zip
|
|
235
|
+
|
|
236
|
+
$State.step = 'checking the Node download'
|
|
237
|
+
$got = (Get-FileHash -Algorithm SHA256 -LiteralPath $zip).Hash.ToLowerInvariant()
|
|
238
|
+
if ($got -ne $want) { Fail "the Node download does not match its published sha256 (expected $want, got $got), so it was not used. Something between you and nodejs.org changed it; run this again on another network." }
|
|
239
|
+
|
|
240
|
+
# Unpacked entry by entry with the archive's top folder stripped, so no
|
|
241
|
+
# path is ever longer than it will be once installed.
|
|
242
|
+
$State.step = 'unpacking Node'
|
|
243
|
+
$new = Join-Path $tmp 'n'
|
|
244
|
+
try { Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop } catch { }
|
|
245
|
+
try { Add-Type -AssemblyName System.IO.Compression -ErrorAction Stop } catch { }
|
|
246
|
+
$archive = [System.IO.Compression.ZipFile]::OpenRead($zip)
|
|
247
|
+
try {
|
|
248
|
+
foreach ($e in $archive.Entries) {
|
|
249
|
+
$rel = $e.FullName -replace '^[^/\\]+[/\\]', ''
|
|
250
|
+
if (-not $rel -or $rel -eq $e.FullName) { continue }
|
|
251
|
+
$dest = Join-Path $new ($rel -replace '/', '\')
|
|
252
|
+
if ($e.FullName.EndsWith('/') -or $e.FullName.EndsWith('\')) { New-Item -ItemType Directory -Force -Path $dest | Out-Null; continue }
|
|
253
|
+
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
|
|
254
|
+
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($e, $dest, $true)
|
|
255
|
+
}
|
|
256
|
+
} finally { $archive.Dispose() }
|
|
257
|
+
$newNode = Join-Path $new 'node.exe'
|
|
258
|
+
if (-not (Test-Path -LiteralPath $newNode)) { Fail 'the Node archive did not have the layout expected. Run this again; if it repeats, send this line to sid@joinpantheon.network' }
|
|
259
|
+
try { $ok = (& $newNode --version) 2>$null } catch { $ok = '' }
|
|
260
|
+
if ($ok -ne "v$NodeVersion") { Fail "Node v$NodeVersion does not run on this Windows. Install Node 22.13 or newer yourself, then: npm install -g @join-pantheon/cli@$PantheonVersion" }
|
|
261
|
+
|
|
262
|
+
# The CLI from the old runtime moves into the new one with it, so the
|
|
263
|
+
# pantheon command keeps working until npm below has run.
|
|
264
|
+
$oldPkgs = Join-Path $nodeDir 'node_modules\@join-pantheon'
|
|
265
|
+
if (Test-Path -LiteralPath $oldPkgs) {
|
|
266
|
+
try { Copy-Item -LiteralPath $oldPkgs -Destination (Join-Path $new 'node_modules') -Recurse -Force } catch { }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Swapped by rename, never unpacked over: a half-written runtime is never
|
|
270
|
+
# the one an agent window starts.
|
|
271
|
+
$State.step = 'putting Node in place'
|
|
272
|
+
$aside = $null
|
|
273
|
+
if (Test-Path -LiteralPath $nodeDir) {
|
|
274
|
+
$aside = Join-Path $tmp 'old'
|
|
275
|
+
# A virus scanner looking at the new or old node.exe holds it for a
|
|
276
|
+
# moment: a few tries before telling the person something is open.
|
|
277
|
+
$moved = $false
|
|
278
|
+
for ($i = 1; $i -le 5 -and -not $moved; $i++) {
|
|
279
|
+
try { Move-Item -LiteralPath $nodeDir -Destination $aside; $moved = $true } catch { Start-Sleep -Seconds 2 }
|
|
280
|
+
}
|
|
281
|
+
if (-not $moved) { Fail "Pantheon's Node in $nodeDir is in use. Close your agent windows (Claude Code, Cursor, VS Code) and any pantheon command, then run this again." }
|
|
282
|
+
}
|
|
283
|
+
$placed = $false; $lastErr = ''
|
|
284
|
+
for ($i = 1; $i -le 5 -and -not $placed; $i++) {
|
|
285
|
+
try { Move-Item -LiteralPath $new -Destination $nodeDir; $placed = $true } catch { $lastErr = $_.Exception.Message; Start-Sleep -Seconds 2 }
|
|
286
|
+
}
|
|
287
|
+
if (-not $placed) {
|
|
288
|
+
if ($aside) { try { Move-Item -LiteralPath $aside -Destination $nodeDir } catch { } }
|
|
289
|
+
Fail "could not put the new Node in $nodeDir ($lastErr). Check free disk space, then run this again."
|
|
290
|
+
}
|
|
291
|
+
Say "Node v$NodeVersion is in $nodeDir."
|
|
292
|
+
} finally {
|
|
293
|
+
# Stopped between the two moves: the old runtime goes back, so the
|
|
294
|
+
# pantheon command keeps working.
|
|
295
|
+
if ($aside -and -not (Test-Path -LiteralPath $nodeDir) -and (Test-Path -LiteralPath $aside)) {
|
|
296
|
+
try { Move-Item -LiteralPath $aside -Destination $nodeDir } catch { }
|
|
297
|
+
}
|
|
298
|
+
Remove-Item -LiteralPath $tmp -Recurse -Force -ErrorAction SilentlyContinue
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# ---- 2. the Pantheon CLI, with that Node's own npm -----------------------
|
|
303
|
+
|
|
304
|
+
# The version of the CLI in the runtime, but only when it is whole: its
|
|
305
|
+
# package.json names that version AND it starts and says so. A run stopped
|
|
306
|
+
# halfway through npm can leave the first without the second; that is
|
|
307
|
+
# installed again, never called done.
|
|
308
|
+
$manifest = Join-Path $pkgDir 'package.json'
|
|
309
|
+
$installedCli = {
|
|
310
|
+
$v = ''
|
|
311
|
+
if ((Test-Path -LiteralPath $manifest) -and (Test-Path -LiteralPath $entry)) {
|
|
312
|
+
try { $v = [string](Get-Content -LiteralPath $manifest -Raw | ConvertFrom-Json).version } catch { $v = '' }
|
|
313
|
+
if ($v -and (Invoke-Quiet $nodeExe @($entry, '--version')).out.Trim() -ne $v) { $v = '' }
|
|
314
|
+
}
|
|
315
|
+
$v
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# What npm installs. PANTHEON_INSTALL_SPEC swaps the registry package for a
|
|
319
|
+
# package file on this machine, so a release candidate goes through this
|
|
320
|
+
# exact script before its version is on npm. Only a full path on a drive
|
|
321
|
+
# (C:\...\x.tgz) is taken: no URL, no \\server share, no git or registry
|
|
322
|
+
# spec, so it can never send an install anywhere npm would not have gone.
|
|
323
|
+
# Whoever can set it can already set npm_config_registry or NODE_OPTIONS
|
|
324
|
+
# for this window, so it opens no new way in; and what it installs must
|
|
325
|
+
# answer as $PantheonVersion (checked below), or the install fails.
|
|
326
|
+
$spec = "@join-pantheon/cli@$PantheonVersion"
|
|
327
|
+
if ($env:PANTHEON_INSTALL_SPEC) {
|
|
328
|
+
$s = $env:PANTHEON_INSTALL_SPEC
|
|
329
|
+
if ($s -notmatch '^[A-Za-z]:\\.*\.tgz$') { Fail "PANTHEON_INSTALL_SPEC must be the full path of a package .tgz on this machine (C:\...\x.tgz), not $s. Remove it to install from npm: Remove-Item Env:PANTHEON_INSTALL_SPEC" }
|
|
330
|
+
if (-not (Test-Path -LiteralPath $s -PathType Leaf)) { Fail "PANTHEON_INSTALL_SPEC is $s, which is not a file. Remove it to install from npm: Remove-Item Env:PANTHEON_INSTALL_SPEC" }
|
|
331
|
+
$spec = $s
|
|
332
|
+
Note "installing Pantheon $PantheonVersion from $s, not from npm (PANTHEON_INSTALL_SPEC is set)."
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# The Windows certificate store, as well as Node's own: on a network that
|
|
336
|
+
# inspects secure traffic, the company's root is only there. npm needs it to
|
|
337
|
+
# install and pantheon needs it to reach its relay, so the launcher below
|
|
338
|
+
# runs Node with it too (when this Node has the flag; the pinned one does).
|
|
339
|
+
$ca = @()
|
|
340
|
+
if ((Invoke-Quiet $nodeExe @('--use-system-ca', '-e', '0')).code -eq 0) { $ca = @('--use-system-ca') }
|
|
341
|
+
|
|
342
|
+
$haveCli = & $installedCli
|
|
343
|
+
if ($haveCli -eq $PantheonVersion) {
|
|
344
|
+
Say "Pantheon $PantheonVersion is already installed."
|
|
345
|
+
} else {
|
|
346
|
+
$State.step = "installing Pantheon $PantheonVersion"
|
|
347
|
+
Say "Installing Pantheon $PantheonVersion..."
|
|
348
|
+
# npm.cmd only runs node.exe on npm-cli.js; doing that directly keeps
|
|
349
|
+
# cmd.exe, and its way with % and & in a path, out of it. The prefix is
|
|
350
|
+
# named, because the Node zip's own npmrc sends global installs to
|
|
351
|
+
# %APPDATA%\npm; the cache is Pantheon's own, so a broken npm cache
|
|
352
|
+
# elsewhere cannot fail this. --use-system-ca (when this Node has it)
|
|
353
|
+
# trusts the Windows certificate store, as Invoke-WebRequest just did,
|
|
354
|
+
# which is what a TLS-inspecting company proxy needs.
|
|
355
|
+
$env:Path = "$nodeDir;$env:Path"
|
|
356
|
+
$npmArgs = $ca + @($npmCli, 'install', '-g', '--prefix', $nodeDir, '--cache', (Join-Path $runtime 'npm-cache'),
|
|
357
|
+
$spec, '--no-audit', '--no-fund', '--loglevel=error', '--fetch-timeout=60000', '--fetch-retries=2')
|
|
358
|
+
$code = Invoke-Logged $nodeExe $npmArgs $log
|
|
359
|
+
if ($code -ne 0) {
|
|
360
|
+
$why = (Get-Content -LiteralPath $log | Where-Object { $_ -match '^npm (error|ERR!) ' } | Select-Object -First 2 | ForEach-Object { $_ -replace '^npm (error|ERR!) ', '' }) -join '; '
|
|
361
|
+
if (-not $why) { $why = "npm exited $code" }
|
|
362
|
+
if ((Get-Content -LiteralPath $log -Raw) -match 'ENOSPC') {
|
|
363
|
+
Fail "npm could not install @join-pantheon/cli@$PantheonVersion because the disk is full ($why). Free some disk space, then run this again."
|
|
364
|
+
}
|
|
365
|
+
Fail "npm could not install @join-pantheon/cli@$PantheonVersion ($why). Behind a proxy, set HTTPS_PROXY; if your network inspects TLS, set NODE_EXTRA_CA_CERTS to your company's CA file. Full log: $log"
|
|
366
|
+
}
|
|
367
|
+
if (-not (Test-Path -LiteralPath $entry)) { Fail "npm finished but $entry is not there. Full log: $log (send it to sid@joinpantheon.network)" }
|
|
368
|
+
$haveCli = & $installedCli
|
|
369
|
+
if ($haveCli -ne $PantheonVersion) { Fail "npm finished, but the Pantheon in $pkgDir does not start as $PantheonVersion$(if ($haveCli) { " (it is $haveCli)" }). Run the same command again; if it fails the same way, send this line and $log to sid@joinpantheon.network" }
|
|
370
|
+
Say "Pantheon $PantheonVersion is installed."
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
# ---- 3. the launcher -----------------------------------------------------
|
|
374
|
+
|
|
375
|
+
# %~dp0 is the folder this .cmd is in, so the file holds no path of its own:
|
|
376
|
+
# nothing in it depends on PATH, and a profile folder with a % or a
|
|
377
|
+
# non-ASCII letter in its name (which cmd.exe would read in the console's
|
|
378
|
+
# code page) cannot break it.
|
|
379
|
+
$State.step = "writing $launcher"
|
|
380
|
+
$cmdText = "@echo off`r`n" +
|
|
381
|
+
"rem The pantheon command, written by the Pantheon installer. It runs Pantheon's own`r`n" +
|
|
382
|
+
"rem Node by absolute path, so it never depends on which node comes first on PATH.`r`n" +
|
|
383
|
+
"`"%~dp0..\runtime\node\node.exe`" $(if ($ca.Count) { '--use-system-ca ' })`"%~dp0..\runtime\node\node_modules\@join-pantheon\cli\bin\pantheon.js`" %*`r`n"
|
|
384
|
+
$current = if (Test-Path -LiteralPath $launcher) { [IO.File]::ReadAllText($launcher) } else { '' }
|
|
385
|
+
if ($current -ne $cmdText) {
|
|
386
|
+
try { [IO.File]::WriteAllText($launcher, $cmdText, [Text.Encoding]::ASCII) }
|
|
387
|
+
catch { Fail "could not write $launcher ($($_.Exception.Message)). Close any window running pantheon, then run this again." }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# ---- 4. PATH -------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
$State.step = 'adding Pantheon to your PATH'
|
|
393
|
+
$same = { param($a, $b) ([Environment]::ExpandEnvironmentVariables($a).TrimEnd('\')) -ieq ([Environment]::ExpandEnvironmentVariables($b).TrimEnd('\')) }
|
|
394
|
+
# Read raw from the registry: [Environment]::GetEnvironmentVariable expands
|
|
395
|
+
# %USERPROFILE%-style entries, and writing that back would freeze them.
|
|
396
|
+
$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
|
|
397
|
+
try {
|
|
398
|
+
$raw = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
|
399
|
+
$parts = @($raw -split ';' | Where-Object { $_ -ne '' })
|
|
400
|
+
if ($parts.Count -eq 0 -or -not (& $same $parts[0] $binDir)) {
|
|
401
|
+
$rest = @($parts | Where-Object { -not (& $same $_ $binDir) })
|
|
402
|
+
$key.SetValue('Path', ((@($binDir) + $rest) -join ';'), [Microsoft.Win32.RegistryValueKind]::ExpandString)
|
|
403
|
+
# SetEnvironmentVariable(..., 'User') is what tells Explorer and new
|
|
404
|
+
# terminals that the user environment changed (WM_SETTINGCHANGE).
|
|
405
|
+
[Environment]::SetEnvironmentVariable('PANTHEON_PATH_UPDATED', '1', 'User')
|
|
406
|
+
[Environment]::SetEnvironmentVariable('PANTHEON_PATH_UPDATED', $null, 'User')
|
|
407
|
+
Say "Put $binDir first on your user PATH."
|
|
408
|
+
}
|
|
409
|
+
} finally { $key.Close() }
|
|
410
|
+
# This window: the bin folder first, and the runtime's own folder (added
|
|
411
|
+
# for npm above) taken off again, as no new terminal will have it.
|
|
412
|
+
$env:Path = (@($binDir) + @($env:Path -split ';' | Where-Object { $_ -ne '' -and -not (& $same $_ $binDir) -and -not (& $same $_ $nodeDir) })) -join ';'
|
|
413
|
+
|
|
414
|
+
# An older pantheon (npm -g, a checkout) is left exactly as it is. A
|
|
415
|
+
# pantheon on the SYSTEM PATH still comes before any user PATH entry.
|
|
416
|
+
$other = Get-Command pantheon -All -ErrorAction SilentlyContinue |
|
|
417
|
+
Where-Object { $_.Source -and -not $_.Source.StartsWith($root + '\', [StringComparison]::OrdinalIgnoreCase) } | Select-Object -First 1
|
|
418
|
+
if ($other) { Note "another pantheon is installed at $($other.Source) and was left as it is. New terminals run $launcher first, unless that one is on the system PATH." }
|
|
419
|
+
|
|
420
|
+
# ---- 5. hand over to pantheon ------------------------------------------
|
|
421
|
+
|
|
422
|
+
$State.lock.Dispose()
|
|
423
|
+
$State.lock = $null
|
|
424
|
+
if ($Arguments.Count -eq 0) {
|
|
425
|
+
Say ''
|
|
426
|
+
Say "Pantheon $PantheonVersion is installed. Open a new terminal, then:"
|
|
427
|
+
Say ' pantheon start create a team'
|
|
428
|
+
Say " pantheon join '<invite link>' join a teammate's team"
|
|
429
|
+
$State.code = 0
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
Say ''
|
|
433
|
+
# node.exe directly, not the .cmd: PowerShell hands arguments to a .cmd
|
|
434
|
+
# through cmd.exe, which would cut an invite at a & or expand a %. And
|
|
435
|
+
# Start-Process -NoNewWindow, so pantheon gets this console itself: a real
|
|
436
|
+
# terminal for the sign-in questions, not a pipe PowerShell reads from.
|
|
437
|
+
$State.step = 'starting pantheon'
|
|
438
|
+
$line = ConvertTo-ArgLine (@($entry) + $Arguments)
|
|
439
|
+
$noConsole = $false
|
|
440
|
+
try { $noConsole = [Console]::IsInputRedirected } catch { $noConsole = $false }
|
|
441
|
+
if (-not $noConsole) {
|
|
442
|
+
$p = Start-Process -FilePath $nodeExe -ArgumentList $line -NoNewWindow -PassThru
|
|
443
|
+
$null = $p.Handle # without this, ExitCode is empty once the process is gone
|
|
444
|
+
$p.WaitForExit()
|
|
445
|
+
$State.code = $p.ExitCode
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
# No console to type into (CI, input piped in). pantheon still runs: it
|
|
450
|
+
# never asks a question without a terminal (every prompt checks
|
|
451
|
+
# process.stdin.isTTY) and works on flags alone, so a join to a team's own
|
|
452
|
+
# relay, or from a machine already signed in, needs no answers. Its input
|
|
453
|
+
# is an empty file, so nothing can wait on it. What it cannot do here is
|
|
454
|
+
# sign in: on the hosted relay join and start stop at once, before anything
|
|
455
|
+
# is changed, with "sign in first" (or the relay's NOT_SIGNED_IN). When that
|
|
456
|
+
# is how it ended and config.json still has no account, the last line says
|
|
457
|
+
# the one thing to do; any other failure keeps pantheon's own words last.
|
|
458
|
+
$quiet = Join-Path ([IO.Path]::GetTempPath()) ('pantheon-' + [Guid]::NewGuid().ToString('N').Substring(0, 8))
|
|
459
|
+
New-Item -ItemType Directory -Force -Path $quiet | Out-Null
|
|
460
|
+
try {
|
|
461
|
+
$in = Join-Path $quiet 'in'; $errFile = Join-Path $quiet 'err'
|
|
462
|
+
[IO.File]::WriteAllText($in, '')
|
|
463
|
+
$p = Start-Process -FilePath $nodeExe -ArgumentList $line -NoNewWindow -PassThru -RedirectStandardInput $in -RedirectStandardError $errFile
|
|
464
|
+
$null = $p.Handle
|
|
465
|
+
$p.WaitForExit()
|
|
466
|
+
$State.code = $p.ExitCode
|
|
467
|
+
$said = if (Test-Path -LiteralPath $errFile) { [IO.File]::ReadAllText($errFile) } else { '' }
|
|
468
|
+
if ($said) { [Console]::Error.Write($said) }
|
|
469
|
+
if ($State.code -ne 0 -and $said -match 'sign in first|NOT_SIGNED_IN' -and $Arguments[0] -in @('join', 'start')) {
|
|
470
|
+
$pantheonHome = if ($env:PANTHEON_HOME) { $env:PANTHEON_HOME } else { Join-Path ([Environment]::GetFolderPath('UserProfile')) '.pantheon' }
|
|
471
|
+
$signedIn = $false
|
|
472
|
+
try { $signedIn = [bool]((Get-Content -LiteralPath (Join-Path $pantheonHome 'config.json') -Raw | ConvertFrom-Json).account) } catch { $signedIn = $false }
|
|
473
|
+
if (-not $signedIn) {
|
|
474
|
+
# Quoted only where PowerShell needs it, so it reads as typed.
|
|
475
|
+
$show = ($Arguments | ForEach-Object { if ($_ -match '^[A-Za-z0-9_./:=@+,-]+$') { $_ } else { "'" + ($_ -replace "'", "''") + "'" } }) -join ' '
|
|
476
|
+
$what = if ($Arguments[0] -eq 'join') { 'joining' } else { 'creating your team' }
|
|
477
|
+
Say ''
|
|
478
|
+
Say "Pantheon is installed. To finish $what, open PowerShell and run: pantheon $show"
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
} finally {
|
|
482
|
+
Remove-Item -LiteralPath $quiet -Recurse -Force -ErrorAction SilentlyContinue
|
|
483
|
+
}
|
|
484
|
+
} catch {
|
|
485
|
+
$m = $_.Exception.Message
|
|
486
|
+
if ($m -like 'PANTHEON_FAIL:*') { $m = $m.Substring('PANTHEON_FAIL:'.Length) }
|
|
487
|
+
else { $m = "it stopped while $($State.step) ($m). Run the same command again; if it fails the same way, send this line to sid@joinpantheon.network" }
|
|
488
|
+
Write-Host "pantheon install failed: $m" -ForegroundColor Red
|
|
489
|
+
$State.code = 1
|
|
490
|
+
} finally {
|
|
491
|
+
# Every way out, Ctrl-C included, lets go of the install lock.
|
|
492
|
+
if ($State.lock) { $State.lock.Dispose(); $State.lock = $null }
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
# `join '<link>'` as two arguments: whitespace splits, and single or double
|
|
497
|
+
# quotes keep a value with spaces in it together.
|
|
498
|
+
function Split-PantheonArgs([string]$Line) {
|
|
499
|
+
$out = New-Object System.Collections.Generic.List[string]
|
|
500
|
+
$cur = New-Object System.Text.StringBuilder
|
|
501
|
+
$quote = [char]0
|
|
502
|
+
$have = $false
|
|
503
|
+
foreach ($c in $Line.ToCharArray()) {
|
|
504
|
+
if ($quote -ne [char]0) {
|
|
505
|
+
if ($c -eq $quote) { $quote = [char]0 } else { [void]$cur.Append($c) }
|
|
506
|
+
} elseif ($c -eq "'" -or $c -eq '"') { $quote = $c; $have = $true }
|
|
507
|
+
elseif ([char]::IsWhiteSpace($c)) { if ($have -or $cur.Length) { $out.Add($cur.ToString()); [void]$cur.Clear(); $have = $false } }
|
|
508
|
+
else { [void]$cur.Append($c) }
|
|
509
|
+
}
|
|
510
|
+
if ($have -or $cur.Length) { $out.Add($cur.ToString()) }
|
|
511
|
+
return , $out.ToArray()
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
# Whether this PowerShell was started to run one command and end
|
|
515
|
+
# (`powershell -Command "... | iex"` from cmd.exe), where the exit code is the
|
|
516
|
+
# result, rather than an open window, which `exit` would close. PowerShell
|
|
517
|
+
# takes any prefix of a parameter's name, after -, -- or /; -NoExit anywhere
|
|
518
|
+
# keeps the window open, so then it is never one-shot.
|
|
519
|
+
function Test-PantheonOneShot([string[]]$CommandLine) {
|
|
520
|
+
$flags = @($CommandLine | Select-Object -Skip 1 | Where-Object { $_ -match '^(--?|/)[A-Za-z]+$' } |
|
|
521
|
+
ForEach-Object { ($_ -replace '^(--?|/)', '').ToLowerInvariant() })
|
|
522
|
+
$prefixOf = { param($f, $name, $min) $f.Length -ge $min -and $name.StartsWith($f) }
|
|
523
|
+
if ($flags | Where-Object { & $prefixOf $_ 'noexit' 3 }) { return $false }
|
|
524
|
+
foreach ($f in $flags) {
|
|
525
|
+
if ((& $prefixOf $f 'command' 1) -or (& $prefixOf $f 'commandwithargs' 8) -or $f -eq 'cwa' -or
|
|
526
|
+
(& $prefixOf $f 'encodedcommand' 1) -or $f -eq 'ec') { return $true }
|
|
527
|
+
}
|
|
528
|
+
return $false
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if ($PantheonInstallArgs.Count -eq 0 -and $env:PANTHEON_ARGS) {
|
|
532
|
+
$PantheonInstallArgs = Split-PantheonArgs $env:PANTHEON_ARGS
|
|
533
|
+
}
|
|
534
|
+
Remove-Item Env:PANTHEON_ARGS -ErrorAction SilentlyContinue
|
|
535
|
+
$PantheonInstallState = @{ code = 1; step = 'starting' }
|
|
536
|
+
Install-Pantheon -Arguments $PantheonInstallArgs -State $PantheonInstallState
|
|
537
|
+
$PantheonInstallCode = [int]$PantheonInstallState.code
|
|
538
|
+
|
|
539
|
+
# `powershell -Command "... | iex"` from cmd.exe: the exit code is the result.
|
|
540
|
+
# In an open PowerShell window `exit` would close the window, so there the
|
|
541
|
+
# result is left in $LASTEXITCODE instead.
|
|
542
|
+
$PantheonOneShot = $false
|
|
543
|
+
try { $PantheonOneShot = Test-PantheonOneShot ([Environment]::GetCommandLineArgs()) } catch { $PantheonOneShot = $false }
|
|
544
|
+
Remove-Item Function:Install-Pantheon, Function:Split-PantheonArgs, Function:Test-PantheonOneShot -ErrorAction SilentlyContinue
|
|
545
|
+
Remove-Variable PantheonInstallArgs, PantheonInstallState -ErrorAction SilentlyContinue
|
|
546
|
+
$global:LASTEXITCODE = $PantheonInstallCode
|
|
547
|
+
if ($PantheonOneShot) { exit $PantheonInstallCode }
|