@mutmutco/installer-face 0.4.4 → 0.4.5
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/CHANGELOG.md +7 -0
- package/dist/face.d.ts +2 -0
- package/dist/index.js +118 -26
- package/dist/install.html.template +261 -0
- package/dist/install.ps1 +250 -0
- package/dist/install.sh +157 -0
- package/dist/run.d.ts +5 -2
- package/dist/spinner.d.ts +2 -0
- package/package.json +4 -1
package/dist/install.ps1
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
# One-line installer for %%PRODUCT%% (launcher %%VERSION%%).
|
|
3
|
+
# Served at %%BASE%% -- run with:
|
|
4
|
+
# irm %%BASE%% | iex
|
|
5
|
+
#
|
|
6
|
+
# The origin substitutes %%PRODUCT%%, %%BASE%% and %%VERSION%% before serving this file.
|
|
7
|
+
# The product name is never read from an argument, an environment variable or a prompt.
|
|
8
|
+
#
|
|
9
|
+
# Windows PowerShell 5.1 compatible: no ternary operator, no null-coalescing, no pwsh-only cmdlets.
|
|
10
|
+
|
|
11
|
+
$ErrorActionPreference = 'Stop'
|
|
12
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
13
|
+
|
|
14
|
+
$Product = '%%PRODUCT%%'
|
|
15
|
+
$Base = '%%BASE%%'
|
|
16
|
+
$Version = '%%VERSION%%'
|
|
17
|
+
|
|
18
|
+
%%FACE%%
|
|
19
|
+
|
|
20
|
+
# A refusal wears the face too (#6789): `Write-FaceRefusal` draws it, so an install that succeeds
|
|
21
|
+
# looks like the product and one that fails does not read as a bare shell error — the moment a
|
|
22
|
+
# person most needs to see who is talking. Still stderr, still plain when piped or NO_COLOR; the
|
|
23
|
+
# package's block sets [Console]::OutputEncoding to UTF-8 before the first glyph.
|
|
24
|
+
function Write-Refusal {
|
|
25
|
+
param([string] $Message)
|
|
26
|
+
Write-FaceRefusal $Message
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Maps the running Windows architecture to the artifact token. Returns '' when unsupported.
|
|
30
|
+
# SHA-256 without a cmdlet (#6803). `Get-FileHash` lives in Microsoft.PowerShell.Utility and is
|
|
31
|
+
# normally autoloaded — but this script is pasted into WHATEVER shell the person happens to have,
|
|
32
|
+
# and a PSModulePath inherited from PowerShell 7 left Windows PowerShell 5.1 unable to resolve its
|
|
33
|
+
# own Utility cmdlets: the install downloaded, then died at the checksum. The verification step is
|
|
34
|
+
# the one place that must never depend on luck, so it uses .NET, which exists wherever PowerShell
|
|
35
|
+
# runs at all. Returns lowercase hex, or '' when the file cannot be read.
|
|
36
|
+
function Get-Sha256Hex {
|
|
37
|
+
param([string] $Path)
|
|
38
|
+
$sha = $null
|
|
39
|
+
$stream = $null
|
|
40
|
+
try {
|
|
41
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
42
|
+
$stream = [System.IO.File]::OpenRead($Path)
|
|
43
|
+
$bytes = $sha.ComputeHash($stream)
|
|
44
|
+
$builder = New-Object System.Text.StringBuilder
|
|
45
|
+
foreach ($b in $bytes) { [void] $builder.Append($b.ToString('x2')) }
|
|
46
|
+
return $builder.ToString()
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return ''
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
if ($null -ne $stream) { $stream.Dispose() }
|
|
53
|
+
if ($null -ne $sha) { $sha.Dispose() }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# Download without a cmdlet, for the same reason (#6803): `Invoke-WebRequest` is Utility too, and a
|
|
58
|
+
# shell that cannot autoload it cannot install at all. TLS 1.2 is forced because 5.1 still defaults
|
|
59
|
+
# to SSL3/TLS1 on older builds and the box refuses those. Returns $true on success.
|
|
60
|
+
function Save-Download {
|
|
61
|
+
param([string] $Uri, [string] $OutFile, [string] $UserAgent)
|
|
62
|
+
try {
|
|
63
|
+
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
# An older .NET without the enum value: leave the default and let the request decide.
|
|
67
|
+
}
|
|
68
|
+
$client = $null
|
|
69
|
+
try {
|
|
70
|
+
$client = New-Object System.Net.WebClient
|
|
71
|
+
$client.Headers.Add('User-Agent', $UserAgent)
|
|
72
|
+
$client.DownloadFile($Uri, $OutFile)
|
|
73
|
+
return $true
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return $false
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
if ($null -ne $client) { $client.Dispose() }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function Get-InstallerArch {
|
|
84
|
+
$raw = $env:PROCESSOR_ARCHITEW6432
|
|
85
|
+
if ([string]::IsNullOrEmpty($raw)) {
|
|
86
|
+
$raw = $env:PROCESSOR_ARCHITECTURE
|
|
87
|
+
}
|
|
88
|
+
if ($raw -eq 'AMD64') {
|
|
89
|
+
return 'x64'
|
|
90
|
+
}
|
|
91
|
+
return ''
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# True when $Directory is already one of the entries in the ';'-separated $PathValue.
|
|
95
|
+
function Test-PathEntry {
|
|
96
|
+
param([string] $PathValue, [string] $Directory)
|
|
97
|
+
if ([string]::IsNullOrEmpty($PathValue)) {
|
|
98
|
+
return $false
|
|
99
|
+
}
|
|
100
|
+
$wanted = $Directory.TrimEnd('\')
|
|
101
|
+
foreach ($entry in $PathValue.Split(';')) {
|
|
102
|
+
$candidate = $entry.Trim().TrimEnd('\')
|
|
103
|
+
if ($candidate.Length -gt 0 -and $candidate -ieq $wanted) {
|
|
104
|
+
return $true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return $false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function Install-Launcher {
|
|
111
|
+
[Net.ServicePointManager]::SecurityProtocol =
|
|
112
|
+
[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
|
113
|
+
|
|
114
|
+
Write-FaceWelcome
|
|
115
|
+
|
|
116
|
+
$arch = Get-InstallerArch
|
|
117
|
+
if ($arch -ne 'x64') {
|
|
118
|
+
Write-Refusal "Unsupported CPU architecture for $Product on Windows; only x64 is supported."
|
|
119
|
+
return 1
|
|
120
|
+
}
|
|
121
|
+
Write-FaceStep "Checked this machine: Windows $arch" ([DateTime]::UtcNow)
|
|
122
|
+
|
|
123
|
+
$url = "https://$Base/dl/$Product/win-$arch"
|
|
124
|
+
$userAgent = "$Product-installer/$Version"
|
|
125
|
+
$tmpDir = Join-Path $env:TEMP ("$Product-install-" + [Guid]::NewGuid().ToString('N'))
|
|
126
|
+
$exitCode = 1
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
[void] [System.IO.Directory]::CreateDirectory($tmpDir)
|
|
130
|
+
$payload = Join-Path $tmpDir 'payload'
|
|
131
|
+
$sumFile = Join-Path $tmpDir 'payload.sha256'
|
|
132
|
+
|
|
133
|
+
$stepStarted = [DateTime]::UtcNow
|
|
134
|
+
if (-not (Save-Download $url $payload $userAgent)) {
|
|
135
|
+
Write-Refusal "Could not download $Product from $url."
|
|
136
|
+
return 1
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (-not (Save-Download "$url.sha256" $sumFile $userAgent)) {
|
|
140
|
+
Write-Refusal "Could not download the checksum from $url.sha256."
|
|
141
|
+
return 1
|
|
142
|
+
}
|
|
143
|
+
Write-FaceStep 'Downloaded the launcher' $stepStarted
|
|
144
|
+
|
|
145
|
+
$expected = ''
|
|
146
|
+
try {
|
|
147
|
+
$firstLine = [System.IO.File]::ReadAllLines($sumFile) | Select-Object -First 1
|
|
148
|
+
if ($null -ne $firstLine) {
|
|
149
|
+
$fields = ([string] $firstLine).Trim() -split '\s+'
|
|
150
|
+
$expected = $fields[0]
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
$expected = ''
|
|
155
|
+
}
|
|
156
|
+
if ([string]::IsNullOrEmpty($expected)) {
|
|
157
|
+
Write-Refusal "The checksum published for $Product is empty or malformed."
|
|
158
|
+
return 1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
$stepStarted = [DateTime]::UtcNow
|
|
162
|
+
$actual = Get-Sha256Hex $payload
|
|
163
|
+
if ([string]::IsNullOrEmpty($actual)) {
|
|
164
|
+
Write-Refusal "Could not read the downloaded file to verify it; nothing was installed."
|
|
165
|
+
return 1
|
|
166
|
+
}
|
|
167
|
+
if ($actual.ToLowerInvariant() -ne $expected.ToLowerInvariant()) {
|
|
168
|
+
Write-Refusal "Checksum mismatch for $Product; the download was discarded and nothing was installed."
|
|
169
|
+
return 1
|
|
170
|
+
}
|
|
171
|
+
Write-FaceStep 'Verified the SHA-256 checksum' $stepStarted
|
|
172
|
+
|
|
173
|
+
$stepStarted = [DateTime]::UtcNow
|
|
174
|
+
$binDir = Join-Path $env:LOCALAPPDATA (Join-Path $Product 'bin')
|
|
175
|
+
$exePath = Join-Path $binDir "$Product.exe"
|
|
176
|
+
try {
|
|
177
|
+
[void] [System.IO.Directory]::CreateDirectory($binDir)
|
|
178
|
+
if ([System.IO.File]::Exists($exePath)) { [System.IO.File]::Delete($exePath) }
|
|
179
|
+
[System.IO.File]::Move($payload, $exePath)
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
Write-Refusal "Could not install $Product into $binDir."
|
|
183
|
+
return 1
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
188
|
+
if ($null -eq $userPath) {
|
|
189
|
+
$userPath = ''
|
|
190
|
+
}
|
|
191
|
+
if (-not (Test-PathEntry $userPath $binDir)) {
|
|
192
|
+
if ($userPath.Length -eq 0) {
|
|
193
|
+
$newPath = $binDir
|
|
194
|
+
}
|
|
195
|
+
elseif ($userPath.EndsWith(';')) {
|
|
196
|
+
$newPath = $userPath + $binDir
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
$newPath = $userPath + ';' + $binDir
|
|
200
|
+
}
|
|
201
|
+
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
Write-Refusal "Could not add $binDir to your user PATH."
|
|
206
|
+
return 1
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
$env:Path = $binDir + ';' + $env:Path
|
|
210
|
+
Write-FaceStep 'Installed the launcher' $stepStarted
|
|
211
|
+
|
|
212
|
+
$script:MMInstallerLauncherPath = $exePath
|
|
213
|
+
$exitCode = 0
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
Remove-Item -LiteralPath $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return $exitCode
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
$installerExitCode = Install-Launcher
|
|
223
|
+
if ($installerExitCode -eq 0) {
|
|
224
|
+
# Invoke outside the setup function's captured return pipeline so the native launcher inherits
|
|
225
|
+
# the terminal. It continues our welcome and owns the only final receipt.
|
|
226
|
+
$previousOuter = [Environment]::GetEnvironmentVariable('MM_OUTER_CONSOLE', 'Process')
|
|
227
|
+
$previousContinues = [Environment]::GetEnvironmentVariable('MM_FACE_CONTINUES', 'Process')
|
|
228
|
+
try {
|
|
229
|
+
[Environment]::SetEnvironmentVariable('MM_OUTER_CONSOLE', $null, 'Process')
|
|
230
|
+
$env:MM_FACE_CONTINUES = 'welcome,preflight'
|
|
231
|
+
$global:LASTEXITCODE = 0
|
|
232
|
+
& $script:MMInstallerLauncherPath install
|
|
233
|
+
$installerExitCode = $LASTEXITCODE
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
if ($LASTEXITCODE -ne 0) {
|
|
237
|
+
$installerExitCode = $LASTEXITCODE
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
Write-Refusal "Could not start the $Product launcher; installation did not finish."
|
|
241
|
+
$installerExitCode = 1
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
finally {
|
|
245
|
+
[Environment]::SetEnvironmentVariable('MM_OUTER_CONSOLE', $previousOuter, 'Process')
|
|
246
|
+
[Environment]::SetEnvironmentVariable('MM_FACE_CONTINUES', $previousContinues, 'Process')
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
# IEX may run inside a caller script too. Never terminate that caller's session.
|
|
250
|
+
$global:LASTEXITCODE = $installerExitCode
|
package/dist/install.sh
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# One-line installer for %%PRODUCT%% (launcher %%VERSION%%).
|
|
3
|
+
# Served at %%BASE%% — run with:
|
|
4
|
+
# curl -fsSL %%BASE%% | sh
|
|
5
|
+
#
|
|
6
|
+
# The origin substitutes %%PRODUCT%%, %%BASE%% and %%VERSION%% before serving this file.
|
|
7
|
+
# The product name is never read from an argument, an environment variable or a prompt.
|
|
8
|
+
#
|
|
9
|
+
# POSIX sh only (dash-safe): no bashisms, no eval of downloaded content, curl only.
|
|
10
|
+
|
|
11
|
+
set -u
|
|
12
|
+
|
|
13
|
+
PRODUCT='%%PRODUCT%%'
|
|
14
|
+
BASE='%%BASE%%'
|
|
15
|
+
VERSION='%%VERSION%%'
|
|
16
|
+
|
|
17
|
+
%%FACE%%
|
|
18
|
+
|
|
19
|
+
# A refusal wears the face too (#6789): `face_refusal` draws it, so an install that succeeds looks
|
|
20
|
+
# like the product and one that fails does not read as a bare shell error — the moment a person
|
|
21
|
+
# most needs to see who is talking. Still stderr, still plain when piped or NO_COLOR.
|
|
22
|
+
fail() {
|
|
23
|
+
face_refusal "$1"
|
|
24
|
+
exit 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# >>> installer-common:begin
|
|
28
|
+
# Pure mapping helpers. installer/scripts/test/run-tests.sh extracts this block and
|
|
29
|
+
# unit-tests it on any host, so keep it free of side effects and of $PRODUCT/$BASE.
|
|
30
|
+
|
|
31
|
+
# detect_arch <uname -m output> -> arm64 | x64, non-zero exit when unsupported.
|
|
32
|
+
detect_arch() {
|
|
33
|
+
case "${1:-}" in
|
|
34
|
+
arm64|aarch64) printf '%s\n' 'arm64' ;;
|
|
35
|
+
x86_64) printf '%s\n' 'x64' ;;
|
|
36
|
+
*) return 1 ;;
|
|
37
|
+
esac
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# map_platform <uname -s output> -> darwin | win, non-zero exit when unsupported.
|
|
41
|
+
map_platform() {
|
|
42
|
+
case "${1:-}" in
|
|
43
|
+
Darwin) printf '%s\n' 'darwin' ;;
|
|
44
|
+
MINGW*|MSYS*|CYGWIN*|Windows_NT) printf '%s\n' 'win' ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
# <<< installer-common:end
|
|
49
|
+
|
|
50
|
+
# sha256_of <file> -> lowercase hex digest on stdout, empty when no tool is available.
|
|
51
|
+
sha256_of() {
|
|
52
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
53
|
+
shasum -a 256 "$1" | cut -d' ' -f1
|
|
54
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
55
|
+
sha256sum "$1" | cut -d' ' -f1
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# ensure_path_entry <rc file> — append one marked block, only when the marker is absent.
|
|
60
|
+
ensure_path_entry() {
|
|
61
|
+
ensure_rc="$1"
|
|
62
|
+
ensure_marker="# >>> $PRODUCT installer >>>"
|
|
63
|
+
if [ -f "$ensure_rc" ] && grep -Fq "$ensure_marker" "$ensure_rc"; then
|
|
64
|
+
return 0
|
|
65
|
+
fi
|
|
66
|
+
{
|
|
67
|
+
printf '\n%s\n' "$ensure_marker"
|
|
68
|
+
printf '%s\n' 'case ":$PATH:" in'
|
|
69
|
+
printf '%s\n' ' *":$HOME/.local/bin:"*) ;;'
|
|
70
|
+
printf '%s\n' ' *) PATH="$HOME/.local/bin:$PATH" ;;'
|
|
71
|
+
printf '%s\n' 'esac'
|
|
72
|
+
printf '%s\n' 'export PATH'
|
|
73
|
+
printf '%s\n' "# <<< $PRODUCT installer <<<"
|
|
74
|
+
} >>"$ensure_rc" || fail "Could not add $PRODUCT to your PATH in $ensure_rc."
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main() {
|
|
78
|
+
face_welcome
|
|
79
|
+
|
|
80
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
81
|
+
fail "curl is required to install $PRODUCT and was not found."
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
uname_s=$(uname -s)
|
|
85
|
+
uname_m=$(uname -m)
|
|
86
|
+
|
|
87
|
+
platform=$(map_platform "$uname_s") || platform=''
|
|
88
|
+
if [ "$platform" = 'win' ]; then
|
|
89
|
+
fail "This script installs $PRODUCT on macOS. On Windows run: irm $BASE | iex"
|
|
90
|
+
fi
|
|
91
|
+
if [ -z "$platform" ]; then
|
|
92
|
+
if [ "$uname_s" = 'Linux' ]; then
|
|
93
|
+
fail "$PRODUCT does not ship a Linux build; it supports macOS and Windows only."
|
|
94
|
+
fi
|
|
95
|
+
fail "Unsupported operating system: $uname_s. $PRODUCT supports macOS and Windows only."
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
arch=$(detect_arch "$uname_m") ||
|
|
99
|
+
fail "Unsupported CPU architecture: $uname_m. $PRODUCT supports arm64 and x64."
|
|
100
|
+
if [ "$platform" = 'darwin' ] && [ "$arch" = 'x64' ]; then
|
|
101
|
+
fail "Intel Macs are not supported — use an Apple Silicon Mac or Windows."
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
step_started=$(date +%s)
|
|
105
|
+
case "$platform" in
|
|
106
|
+
darwin) face_step "Checked this machine: macOS $arch" "$step_started" ;;
|
|
107
|
+
*) face_step "Checked this machine: $platform $arch" "$step_started" ;;
|
|
108
|
+
esac
|
|
109
|
+
|
|
110
|
+
url="https://$BASE/dl/$PRODUCT/$platform-$arch"
|
|
111
|
+
|
|
112
|
+
tmpdir=$(mktemp -d) || fail "Could not create a temporary directory."
|
|
113
|
+
trap 'rm -rf "$tmpdir"' EXIT
|
|
114
|
+
trap 'rm -rf "$tmpdir"; exit 1' INT TERM
|
|
115
|
+
|
|
116
|
+
step_started=$(date +%s)
|
|
117
|
+
curl -fsSL --proto '=https' --tlsv1.2 -A "$PRODUCT-installer/$VERSION" \
|
|
118
|
+
-o "$tmpdir/payload" "$url" || fail "Could not download $PRODUCT from $url."
|
|
119
|
+
curl -fsSL --proto '=https' --tlsv1.2 -A "$PRODUCT-installer/$VERSION" \
|
|
120
|
+
-o "$tmpdir/payload.sha256" "$url.sha256" || fail "Could not download the checksum from $url.sha256."
|
|
121
|
+
face_step 'Downloaded the launcher' "$step_started"
|
|
122
|
+
|
|
123
|
+
step_started=$(date +%s)
|
|
124
|
+
expected=$(head -n 1 "$tmpdir/payload.sha256" | cut -d' ' -f1 | tr -d '\r')
|
|
125
|
+
[ -n "$expected" ] || fail "The checksum published for $PRODUCT is empty or malformed."
|
|
126
|
+
|
|
127
|
+
actual=$(sha256_of "$tmpdir/payload")
|
|
128
|
+
[ -n "$actual" ] || fail "No SHA-256 tool was found; install shasum or sha256sum and retry."
|
|
129
|
+
|
|
130
|
+
if [ "$expected" != "$actual" ]; then
|
|
131
|
+
fail "Checksum mismatch for $PRODUCT; the download was discarded and nothing was installed."
|
|
132
|
+
fi
|
|
133
|
+
face_step 'Verified the SHA-256 checksum' "$step_started"
|
|
134
|
+
|
|
135
|
+
step_started=$(date +%s)
|
|
136
|
+
bindir="$HOME/.local/bin"
|
|
137
|
+
mkdir -p "$bindir" || fail "Could not create $bindir."
|
|
138
|
+
chmod 0755 "$tmpdir/payload" || fail "Could not make $PRODUCT executable."
|
|
139
|
+
mv -f "$tmpdir/payload" "$bindir/$PRODUCT" || fail "Could not install $PRODUCT into $bindir."
|
|
140
|
+
|
|
141
|
+
ensure_path_entry "$HOME/.zshenv"
|
|
142
|
+
ensure_path_entry "$HOME/.bashrc"
|
|
143
|
+
|
|
144
|
+
rm -rf "$tmpdir"
|
|
145
|
+
trap - EXIT INT TERM
|
|
146
|
+
face_step 'Installed the launcher' "$step_started"
|
|
147
|
+
|
|
148
|
+
PATH="$bindir:$PATH"
|
|
149
|
+
export PATH
|
|
150
|
+
|
|
151
|
+
# The bootstrap opened the run; the shared launcher owns its actual outcome and receipt.
|
|
152
|
+
# Exit zero may mean deferred, so the shell must never translate it into a ready claim.
|
|
153
|
+
MM_OUTER_CONSOLE='' MM_FACE_CONTINUES='welcome,preflight' "$bindir/$PRODUCT" install
|
|
154
|
+
return "$?"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
main
|
package/dist/run.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ declare const PHASES: {
|
|
|
22
22
|
readonly 'verify-release': readonly ["Checking the release version", "Verified the release version"];
|
|
23
23
|
readonly verify: readonly ["Verifying the payload", "Verified the payload"];
|
|
24
24
|
readonly install: readonly ["Installing the product", "Installed the product"];
|
|
25
|
+
readonly configure: readonly ["Configuring the product", "Configured the product"];
|
|
25
26
|
readonly activate: readonly ["Activating surfaces", "Activated surfaces"];
|
|
26
27
|
readonly doctor: readonly ["Checking health", "Checked health"];
|
|
27
28
|
readonly rollback: readonly ["Restoring the previous version", "Restored the previous version"];
|
|
@@ -76,16 +77,18 @@ export declare function createInstallerRun(value: unknown, options?: InstallerRu
|
|
|
76
77
|
start: () => void;
|
|
77
78
|
phase(id: InstallerPhase, facts?: InstallerPhaseFacts): void;
|
|
78
79
|
surface(facts: InstallerSurfaceFacts): void;
|
|
79
|
-
milestone({ step, state, ms }: {
|
|
80
|
+
milestone({ step, state, ms, measure }: {
|
|
80
81
|
step: string;
|
|
81
|
-
state: StepKind;
|
|
82
|
+
state: StepKind | "running";
|
|
82
83
|
ms?: number;
|
|
84
|
+
measure?: string;
|
|
83
85
|
}): void;
|
|
84
86
|
signIn({ url, code }: {
|
|
85
87
|
url: string;
|
|
86
88
|
code: string;
|
|
87
89
|
}): void;
|
|
88
90
|
relay(text: string, channel?: InstallerChannel, record?: boolean): void;
|
|
91
|
+
relayChunk(text: string, channel?: InstallerChannel): void;
|
|
89
92
|
cancel(): void;
|
|
90
93
|
finish(facts: InstallerFinish): void;
|
|
91
94
|
stop(): void;
|
package/dist/spinner.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface Spinner {
|
|
|
6
6
|
say(text: string, measure?: StepMeasure): void;
|
|
7
7
|
/** Stop and CLEAR the line. Always call before writing the durable step line. */
|
|
8
8
|
stop(): void;
|
|
9
|
+
pause(): void;
|
|
10
|
+
resume(): void;
|
|
9
11
|
}
|
|
10
12
|
export interface SpinnerOptions {
|
|
11
13
|
/** False writes nothing at all: no frames, no escape codes, no cleared lines. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/installer-face",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "The MM Terminal Line installer face: one renderer, the canonical product table, shell/PowerShell fragments for served one-liners, and the drift guard every surface runs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"node": ">=22"
|
|
9
9
|
},
|
|
10
10
|
"exports": {
|
|
11
|
+
"./install.html.template": "./dist/install.html.template",
|
|
12
|
+
"./install.sh": "./dist/install.sh",
|
|
13
|
+
"./install.ps1": "./dist/install.ps1",
|
|
11
14
|
".": {
|
|
12
15
|
"types": "./dist/index.d.ts",
|
|
13
16
|
"import": "./dist/index.js",
|