@plusonelabs/cue 0.0.90 → 0.0.93
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.txt +7 -0
- package/bin/cue.js +550 -0
- package/bin/windows-bootstrap.ps1 +1113 -0
- package/bin/windows-runtime-artifact.json +6 -0
- package/dist/cli.mjs +1482 -1831
- package/package.json +5 -2
- package/.eslintignore +0 -6
- package/cue-v0.0.90.tar.gz +0 -0
- package/install.sh +0 -63
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[string]$Distro = "Ubuntu",
|
|
3
|
+
[switch]$InstallCue,
|
|
4
|
+
[switch]$RequireSystemd = $true,
|
|
5
|
+
[switch]$Json,
|
|
6
|
+
[string]$RuntimeUrl = "",
|
|
7
|
+
[string]$RuntimeSha256 = ""
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = "Stop"
|
|
11
|
+
|
|
12
|
+
$BootstrapReport = [ordered]@{
|
|
13
|
+
ok = $false
|
|
14
|
+
distro = $Distro
|
|
15
|
+
requireSystemd = [bool]$RequireSystemd
|
|
16
|
+
installCue = [bool]$InstallCue
|
|
17
|
+
checks = @()
|
|
18
|
+
warnings = @()
|
|
19
|
+
steps = @()
|
|
20
|
+
runtime = $null
|
|
21
|
+
host = [ordered]@{
|
|
22
|
+
version = $null
|
|
23
|
+
}
|
|
24
|
+
cue = [ordered]@{
|
|
25
|
+
installed = $false
|
|
26
|
+
version = $null
|
|
27
|
+
}
|
|
28
|
+
runtimeArtifact = [ordered]@{
|
|
29
|
+
url = $null
|
|
30
|
+
sha256 = $null
|
|
31
|
+
source = $null
|
|
32
|
+
downloaded = $false
|
|
33
|
+
extractedPath = $null
|
|
34
|
+
installMode = $null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if ($RuntimeUrl) {
|
|
39
|
+
$BootstrapReport.runtimeArtifact.url = $RuntimeUrl
|
|
40
|
+
}
|
|
41
|
+
if ($RuntimeSha256) {
|
|
42
|
+
$BootstrapReport.runtimeArtifact.sha256 = $RuntimeSha256
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function Add-Check {
|
|
46
|
+
param(
|
|
47
|
+
[Parameter(Mandatory = $true)]
|
|
48
|
+
[string]$Name,
|
|
49
|
+
[Parameter(Mandatory = $true)]
|
|
50
|
+
[ValidateSet("ok", "warn", "fail")]
|
|
51
|
+
[string]$Status,
|
|
52
|
+
[Parameter(Mandatory = $true)]
|
|
53
|
+
[string]$Message
|
|
54
|
+
)
|
|
55
|
+
$BootstrapReport.checks += [pscustomobject]@{
|
|
56
|
+
name = $Name
|
|
57
|
+
status = $Status
|
|
58
|
+
message = $Message
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Add-Warning {
|
|
63
|
+
param([Parameter(Mandatory = $true)][string]$Message)
|
|
64
|
+
$BootstrapReport.warnings += $Message
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Add-Step {
|
|
68
|
+
param([Parameter(Mandatory = $true)][string]$Message)
|
|
69
|
+
$BootstrapReport.steps += $Message
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function Complete-Bootstrap {
|
|
73
|
+
param(
|
|
74
|
+
[int]$ExitCode = 0,
|
|
75
|
+
[bool]$Ok = $true
|
|
76
|
+
)
|
|
77
|
+
$BootstrapReport.ok = $Ok
|
|
78
|
+
if ($Json) {
|
|
79
|
+
$BootstrapReport | ConvertTo-Json -Depth 6
|
|
80
|
+
}
|
|
81
|
+
exit $ExitCode
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function Write-Step($Message) {
|
|
85
|
+
if (-not $Json) {
|
|
86
|
+
Write-Host "==> $Message"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Write-WarnMessage($Message) {
|
|
91
|
+
Add-Warning $Message
|
|
92
|
+
if (-not $Json) {
|
|
93
|
+
Write-Host "WARN: $Message" -ForegroundColor Yellow
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function Write-Ok($Message) {
|
|
98
|
+
if (-not $Json) {
|
|
99
|
+
Write-Host "OK: $Message" -ForegroundColor Green
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Normalize-WslOutputLine {
|
|
104
|
+
param([string]$Value)
|
|
105
|
+
if ($null -eq $Value) {
|
|
106
|
+
return ""
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
$normalized = [string]$Value
|
|
110
|
+
$normalized = $normalized.Replace([string][char]0, "")
|
|
111
|
+
return $normalized.Trim()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Quote-BashSingle {
|
|
115
|
+
param([string]$Value)
|
|
116
|
+
if ($null -eq $Value) {
|
|
117
|
+
return "''"
|
|
118
|
+
}
|
|
119
|
+
$bashSingleQuoteEscape = "'" + '"' + "'" + '"' + "'"
|
|
120
|
+
return "'" + ($Value -replace "'", $bashSingleQuoteEscape) + "'"
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function Convert-WindowsPathToWslPath {
|
|
124
|
+
param([string]$WindowsPath)
|
|
125
|
+
|
|
126
|
+
if ([string]::IsNullOrWhiteSpace($WindowsPath)) {
|
|
127
|
+
return $null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
$fullPath = [System.IO.Path]::GetFullPath($WindowsPath)
|
|
131
|
+
if ($fullPath -notmatch '^[A-Za-z]:\\') {
|
|
132
|
+
return $null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
$drive = $fullPath.Substring(0, 1).ToLowerInvariant()
|
|
136
|
+
$rest = $fullPath.Substring(3).Replace('\', '/')
|
|
137
|
+
if ([string]::IsNullOrEmpty($rest)) {
|
|
138
|
+
return "/mnt/$drive"
|
|
139
|
+
}
|
|
140
|
+
return "/mnt/$drive/$rest"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function Read-FileBytesSafe {
|
|
144
|
+
param([string]$Path)
|
|
145
|
+
if (-not (Test-Path -LiteralPath $Path)) {
|
|
146
|
+
return [byte[]]@()
|
|
147
|
+
}
|
|
148
|
+
return [System.IO.File]::ReadAllBytes($Path)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function Get-DecodedTextScore {
|
|
152
|
+
param([string]$Text)
|
|
153
|
+
if ($null -eq $Text) {
|
|
154
|
+
return -100000
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
$score = 0
|
|
158
|
+
foreach ($char in $Text.ToCharArray()) {
|
|
159
|
+
$code = [int][char]$char
|
|
160
|
+
|
|
161
|
+
if ($char -eq [char]0) {
|
|
162
|
+
$score -= 8
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if ($char -eq [char]0xFFFD) {
|
|
167
|
+
$score -= 6
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if ([char]::IsControl($char)) {
|
|
172
|
+
if ($char -eq "`r" -or $char -eq "`n" -or $char -eq "`t") {
|
|
173
|
+
$score += 0
|
|
174
|
+
} else {
|
|
175
|
+
$score -= 4
|
|
176
|
+
}
|
|
177
|
+
continue
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if ([char]::IsLetterOrDigit($char) -or [char]::IsWhiteSpace($char)) {
|
|
181
|
+
$score += 2
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Allow punctuation/symbols/CJK without over-penalizing.
|
|
186
|
+
$score += 1
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return $score
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function Test-BytesLikelyUtf16Le {
|
|
193
|
+
param([byte[]]$Bytes)
|
|
194
|
+
if ($null -eq $Bytes -or $Bytes.Length -lt 4) {
|
|
195
|
+
return $false
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
$pairs = 0
|
|
199
|
+
$oddZero = 0
|
|
200
|
+
for ($i = 1; $i -lt $Bytes.Length; $i += 2) {
|
|
201
|
+
$pairs += 1
|
|
202
|
+
if ($Bytes[$i] -eq 0) {
|
|
203
|
+
$oddZero += 1
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ($pairs -eq 0) {
|
|
208
|
+
return $false
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (($oddZero / $pairs) -ge 0.30)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function Test-BytesLikelyUtf16Be {
|
|
215
|
+
param([byte[]]$Bytes)
|
|
216
|
+
if ($null -eq $Bytes -or $Bytes.Length -lt 4) {
|
|
217
|
+
return $false
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
$pairs = 0
|
|
221
|
+
$evenZero = 0
|
|
222
|
+
for ($i = 0; $i -lt $Bytes.Length; $i += 2) {
|
|
223
|
+
$pairs += 1
|
|
224
|
+
if ($Bytes[$i] -eq 0) {
|
|
225
|
+
$evenZero += 1
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if ($pairs -eq 0) {
|
|
230
|
+
return $false
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return (($evenZero / $pairs) -ge 0.30)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function Decode-WslOutputBytes {
|
|
237
|
+
param([byte[]]$Bytes)
|
|
238
|
+
|
|
239
|
+
if ($null -eq $Bytes -or $Bytes.Length -eq 0) {
|
|
240
|
+
return ""
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# BOM handling first
|
|
244
|
+
if ($Bytes.Length -ge 3 -and $Bytes[0] -eq 0xEF -and $Bytes[1] -eq 0xBB -and $Bytes[2] -eq 0xBF) {
|
|
245
|
+
return [System.Text.Encoding]::UTF8.GetString($Bytes, 3, $Bytes.Length - 3)
|
|
246
|
+
}
|
|
247
|
+
if ($Bytes.Length -ge 2 -and $Bytes[0] -eq 0xFF -and $Bytes[1] -eq 0xFE) {
|
|
248
|
+
return [System.Text.Encoding]::Unicode.GetString($Bytes, 2, $Bytes.Length - 2)
|
|
249
|
+
}
|
|
250
|
+
if ($Bytes.Length -ge 2 -and $Bytes[0] -eq 0xFE -and $Bytes[1] -eq 0xFF) {
|
|
251
|
+
return [System.Text.Encoding]::BigEndianUnicode.GetString($Bytes, 2, $Bytes.Length - 2)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# `wsl -l -q` frequently writes UTF-16 text without BOM when redirected.
|
|
255
|
+
if (Test-BytesLikelyUtf16Le $Bytes) {
|
|
256
|
+
return [System.Text.Encoding]::Unicode.GetString($Bytes)
|
|
257
|
+
}
|
|
258
|
+
if (Test-BytesLikelyUtf16Be $Bytes) {
|
|
259
|
+
return [System.Text.Encoding]::BigEndianUnicode.GetString($Bytes)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
$candidates = New-Object System.Collections.Generic.List[object]
|
|
263
|
+
|
|
264
|
+
$unicodeText = [System.Text.Encoding]::Unicode.GetString($Bytes)
|
|
265
|
+
$candidates.Add([pscustomobject]@{ Name = "utf16le"; Text = $unicodeText; Score = (Get-DecodedTextScore $unicodeText) })
|
|
266
|
+
|
|
267
|
+
$beUnicodeText = [System.Text.Encoding]::BigEndianUnicode.GetString($Bytes)
|
|
268
|
+
$candidates.Add([pscustomobject]@{ Name = "utf16be"; Text = $beUnicodeText; Score = (Get-DecodedTextScore $beUnicodeText) })
|
|
269
|
+
|
|
270
|
+
$utf8Text = [System.Text.Encoding]::UTF8.GetString($Bytes)
|
|
271
|
+
$candidates.Add([pscustomobject]@{ Name = "utf8"; Text = $utf8Text; Score = (Get-DecodedTextScore $utf8Text) })
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
$consoleEncoding = [Console]::OutputEncoding
|
|
275
|
+
if ($consoleEncoding) {
|
|
276
|
+
$consoleText = $consoleEncoding.GetString($Bytes)
|
|
277
|
+
$candidates.Add([pscustomobject]@{
|
|
278
|
+
Name = "console"
|
|
279
|
+
Text = $consoleText
|
|
280
|
+
Score = (Get-DecodedTextScore $consoleText)
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
# Ignore console encoding errors and fall back to other candidates.
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
$ansiEncoding = [System.Text.Encoding]::GetEncoding([System.Globalization.CultureInfo]::CurrentCulture.TextInfo.ANSICodePage)
|
|
289
|
+
if ($ansiEncoding) {
|
|
290
|
+
$ansiText = $ansiEncoding.GetString($Bytes)
|
|
291
|
+
$candidates.Add([pscustomobject]@{
|
|
292
|
+
Name = "ansi"
|
|
293
|
+
Text = $ansiText
|
|
294
|
+
Score = (Get-DecodedTextScore $ansiText)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
# Ignore ANSI code page lookup errors.
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
$best = $candidates | Sort-Object -Property Score -Descending | Select-Object -First 1
|
|
302
|
+
if ($null -eq $best) {
|
|
303
|
+
return ""
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return [string]$best.Text
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function Invoke-WslCommandRawCapture {
|
|
310
|
+
param(
|
|
311
|
+
[Parameter(Mandatory = $true)]
|
|
312
|
+
[string[]]$Arguments
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
$stdoutPath = [System.IO.Path]::GetTempFileName()
|
|
316
|
+
$stderrPath = [System.IO.Path]::GetTempFileName()
|
|
317
|
+
try {
|
|
318
|
+
$process = Start-Process -FilePath "wsl.exe" `
|
|
319
|
+
-ArgumentList $Arguments `
|
|
320
|
+
-NoNewWindow `
|
|
321
|
+
-Wait `
|
|
322
|
+
-PassThru `
|
|
323
|
+
-RedirectStandardOutput $stdoutPath `
|
|
324
|
+
-RedirectStandardError $stderrPath
|
|
325
|
+
$code = $process.ExitCode
|
|
326
|
+
|
|
327
|
+
$stdoutText = Decode-WslOutputBytes (Read-FileBytesSafe $stdoutPath)
|
|
328
|
+
$stderrText = Decode-WslOutputBytes (Read-FileBytesSafe $stderrPath)
|
|
329
|
+
} finally {
|
|
330
|
+
Remove-Item -LiteralPath $stdoutPath -ErrorAction SilentlyContinue
|
|
331
|
+
Remove-Item -LiteralPath $stderrPath -ErrorAction SilentlyContinue
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
$lines = @()
|
|
335
|
+
|
|
336
|
+
foreach ($textBlock in @($stdoutText, $stderrText)) {
|
|
337
|
+
if ([string]::IsNullOrWhiteSpace($textBlock)) {
|
|
338
|
+
continue
|
|
339
|
+
}
|
|
340
|
+
foreach ($item in ($textBlock -split "(`r`n|`n|`r)")) {
|
|
341
|
+
$line = Normalize-WslOutputLine ([string]$item)
|
|
342
|
+
if ($line) {
|
|
343
|
+
$lines += $line
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return [pscustomobject]@{
|
|
349
|
+
ExitCode = $code
|
|
350
|
+
Output = ($lines -join [Environment]::NewLine)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function Invoke-WslCommand {
|
|
355
|
+
param(
|
|
356
|
+
[Parameter(Mandatory = $true)]
|
|
357
|
+
[string[]]$Arguments
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
$previousErrorActionPreference = $ErrorActionPreference
|
|
361
|
+
$nativeCommandPreferenceVar = Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue
|
|
362
|
+
$previousNativeCommandPreference = $null
|
|
363
|
+
if ($nativeCommandPreferenceVar) {
|
|
364
|
+
$previousNativeCommandPreference = [bool]$nativeCommandPreferenceVar.Value
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
$ErrorActionPreference = "Continue"
|
|
369
|
+
if ($nativeCommandPreferenceVar) {
|
|
370
|
+
$script:PSNativeCommandUseErrorActionPreference = $false
|
|
371
|
+
}
|
|
372
|
+
$rawOutput = & wsl.exe @Arguments 2>&1
|
|
373
|
+
$code = $LASTEXITCODE
|
|
374
|
+
} finally {
|
|
375
|
+
$ErrorActionPreference = $previousErrorActionPreference
|
|
376
|
+
if ($nativeCommandPreferenceVar) {
|
|
377
|
+
$script:PSNativeCommandUseErrorActionPreference = $previousNativeCommandPreference
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
$lines = @()
|
|
382
|
+
foreach ($item in @($rawOutput)) {
|
|
383
|
+
if ($null -eq $item) {
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
if ($item -is [System.Management.Automation.ErrorRecord]) {
|
|
387
|
+
$line = Normalize-WslOutputLine ($item.ToString())
|
|
388
|
+
if ($line) {
|
|
389
|
+
$lines += $line
|
|
390
|
+
}
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
$line = Normalize-WslOutputLine ([string]$item)
|
|
394
|
+
if ($line) {
|
|
395
|
+
$lines += $line
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return [pscustomobject]@{
|
|
400
|
+
ExitCode = $code
|
|
401
|
+
Output = ($lines -join [Environment]::NewLine)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function Resolve-RuntimeArtifactRoot {
|
|
406
|
+
param([Parameter(Mandatory = $true)][string]$ExtractDir)
|
|
407
|
+
|
|
408
|
+
$directBundle = Join-Path $ExtractDir "cli-release.mjs"
|
|
409
|
+
$directInstaller = Join-Path $ExtractDir "install-in-wsl-user.sh"
|
|
410
|
+
if ((Test-Path -LiteralPath $directBundle) -and (Test-Path -LiteralPath $directInstaller)) {
|
|
411
|
+
return $ExtractDir
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
$wslRuntimeDir = Join-Path $ExtractDir "wsl-runtime"
|
|
415
|
+
$nestedBundle = Join-Path $wslRuntimeDir "cli-release.mjs"
|
|
416
|
+
$nestedInstaller = Join-Path $wslRuntimeDir "install-in-wsl-user.sh"
|
|
417
|
+
if ((Test-Path -LiteralPath $nestedBundle) -and (Test-Path -LiteralPath $nestedInstaller)) {
|
|
418
|
+
return $wslRuntimeDir
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
$bundleFile = Get-ChildItem -LiteralPath $ExtractDir -Recurse -File -Filter "cli-release.mjs" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
422
|
+
if ($bundleFile) {
|
|
423
|
+
$candidateDir = $bundleFile.Directory.FullName
|
|
424
|
+
if (Test-Path -LiteralPath (Join-Path $candidateDir "install-in-wsl-user.sh")) {
|
|
425
|
+
return $candidateDir
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return $null
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function Resolve-ConfiguredRuntimeArtifactSource {
|
|
433
|
+
$envUrl = [string]$env:CUE_WSL_RUNTIME_URL
|
|
434
|
+
$envSha256 = [string]$env:CUE_WSL_RUNTIME_SHA256
|
|
435
|
+
if (-not [string]::IsNullOrWhiteSpace($envUrl)) {
|
|
436
|
+
$resolvedEnvSha256 = $null
|
|
437
|
+
if (-not [string]::IsNullOrWhiteSpace($envSha256)) {
|
|
438
|
+
$resolvedEnvSha256 = $envSha256
|
|
439
|
+
}
|
|
440
|
+
return [pscustomobject]@{
|
|
441
|
+
Url = $envUrl
|
|
442
|
+
Sha256 = $resolvedEnvSha256
|
|
443
|
+
Source = "env"
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
$manifestPath = Join-Path $PSScriptRoot "windows-runtime-artifact.json"
|
|
448
|
+
if (-not (Test-Path -LiteralPath $manifestPath)) {
|
|
449
|
+
return $null
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
$manifestJson = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
|
|
454
|
+
} catch {
|
|
455
|
+
Add-Check -Name "runtime_artifact_source" -Status "warn" -Message "Runtime artifact manifest could not be parsed"
|
|
456
|
+
Add-Step "Verify windows-runtime-artifact.json is valid JSON or rerun bootstrap with -RuntimeUrl"
|
|
457
|
+
return $null
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
$entry = $null
|
|
461
|
+
if ($manifestJson.PSObject.Properties.Name -contains "runtimeArtifact") {
|
|
462
|
+
$entry = $manifestJson.runtimeArtifact
|
|
463
|
+
} else {
|
|
464
|
+
$entry = $manifestJson
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if ($null -eq $entry) {
|
|
468
|
+
return $null
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
$manifestUrl = [string]$entry.url
|
|
472
|
+
if ([string]::IsNullOrWhiteSpace($manifestUrl)) {
|
|
473
|
+
return $null
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
$manifestSha256 = $null
|
|
477
|
+
if ($entry.PSObject.Properties.Name -contains "sha256") {
|
|
478
|
+
$manifestSha256 = [string]$entry.sha256
|
|
479
|
+
if ([string]::IsNullOrWhiteSpace($manifestSha256)) {
|
|
480
|
+
$manifestSha256 = $null
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return [pscustomobject]@{
|
|
485
|
+
Url = $manifestUrl
|
|
486
|
+
Sha256 = $manifestSha256
|
|
487
|
+
Source = "manifest"
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function Resolve-LocalRuntimeArtifactPath {
|
|
492
|
+
param([Parameter(Mandatory = $true)][string]$ArtifactPathOrUrl)
|
|
493
|
+
|
|
494
|
+
if ([string]::IsNullOrWhiteSpace($ArtifactPathOrUrl)) {
|
|
495
|
+
return $null
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
$candidates = New-Object System.Collections.Generic.List[string]
|
|
499
|
+
$candidates.Add($ArtifactPathOrUrl)
|
|
500
|
+
|
|
501
|
+
if (-not [System.IO.Path]::IsPathRooted($ArtifactPathOrUrl)) {
|
|
502
|
+
$candidates.Add((Join-Path $PSScriptRoot $ArtifactPathOrUrl))
|
|
503
|
+
|
|
504
|
+
$scriptParent = Split-Path -Parent $PSScriptRoot
|
|
505
|
+
$scriptGrandparent = Split-Path -Parent $scriptParent
|
|
506
|
+
$artifactBaseName = [System.IO.Path]::GetFileName($ArtifactPathOrUrl)
|
|
507
|
+
if (-not [string]::IsNullOrWhiteSpace($artifactBaseName)) {
|
|
508
|
+
# Fallbacks for test-kit deployments where artifacts may be copied to C:\cue-test
|
|
509
|
+
# instead of C:\cue-test\artifacts.
|
|
510
|
+
$candidates.Add((Join-Path $scriptGrandparent $artifactBaseName))
|
|
511
|
+
$candidates.Add((Join-Path (Join-Path $scriptGrandparent "artifacts") $artifactBaseName))
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
foreach ($candidate in $candidates) {
|
|
516
|
+
if ([string]::IsNullOrWhiteSpace($candidate)) {
|
|
517
|
+
continue
|
|
518
|
+
}
|
|
519
|
+
if (Test-Path -LiteralPath $candidate) {
|
|
520
|
+
return [System.IO.Path]::GetFullPath($candidate)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return $null
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function Install-CueFromRuntimeArtifact {
|
|
528
|
+
param(
|
|
529
|
+
[Parameter(Mandatory = $true)][string]$DistroName,
|
|
530
|
+
[Parameter(Mandatory = $true)][string]$ArtifactUrl,
|
|
531
|
+
[string]$ExpectedSha256
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
$downloadDir = Join-Path ([System.IO.Path]::GetTempPath()) ("cue-wsl-runtime-" + [Guid]::NewGuid().ToString("N"))
|
|
535
|
+
New-Item -ItemType Directory -Path $downloadDir -Force | Out-Null
|
|
536
|
+
$zipPath = Join-Path $downloadDir "cue-wsl-runtime.zip"
|
|
537
|
+
$extractDir = Join-Path $downloadDir "extract"
|
|
538
|
+
|
|
539
|
+
$artifactLocalPath = $null
|
|
540
|
+
$isUriLike = ($ArtifactUrl -match '^[a-zA-Z][a-zA-Z0-9+.-]*://')
|
|
541
|
+
if (-not $isUriLike) {
|
|
542
|
+
$artifactLocalPath = Resolve-LocalRuntimeArtifactPath -ArtifactPathOrUrl $ArtifactUrl
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if ($artifactLocalPath) {
|
|
546
|
+
Write-Step "Using local WSL runtime artifact"
|
|
547
|
+
Copy-Item -LiteralPath $artifactLocalPath -Destination $zipPath -Force
|
|
548
|
+
$BootstrapReport.runtimeArtifact.downloaded = $false
|
|
549
|
+
$BootstrapReport.runtimeArtifact.localPath = $artifactLocalPath
|
|
550
|
+
Add-Check -Name "runtime_artifact_source" -Status "ok" -Message "Using local WSL runtime artifact: $artifactLocalPath"
|
|
551
|
+
} else {
|
|
552
|
+
Write-Step "Downloading WSL runtime artifact"
|
|
553
|
+
try {
|
|
554
|
+
Invoke-WebRequest -Uri $ArtifactUrl -OutFile $zipPath
|
|
555
|
+
} catch {
|
|
556
|
+
Add-Check -Name "runtime_artifact_download" -Status "fail" -Message "Failed to download WSL runtime artifact"
|
|
557
|
+
$BootstrapReport.runtimeArtifact.downloadError = $_.Exception.Message
|
|
558
|
+
throw
|
|
559
|
+
}
|
|
560
|
+
$BootstrapReport.runtimeArtifact.downloaded = $true
|
|
561
|
+
Add-Check -Name "runtime_artifact_download" -Status "ok" -Message "Downloaded WSL runtime artifact"
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if ($ExpectedSha256) {
|
|
565
|
+
Write-Step "Verifying WSL runtime artifact checksum"
|
|
566
|
+
$actualSha256 = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
567
|
+
$BootstrapReport.runtimeArtifact.actualSha256 = $actualSha256
|
|
568
|
+
if ($actualSha256 -ne $ExpectedSha256.ToLowerInvariant()) {
|
|
569
|
+
Add-Check -Name "runtime_artifact_sha256" -Status "fail" -Message "WSL runtime artifact SHA256 mismatch"
|
|
570
|
+
Add-Step "Re-download the runtime artifact and verify the URL/version"
|
|
571
|
+
throw "Runtime artifact SHA256 mismatch. Expected $ExpectedSha256, got $actualSha256"
|
|
572
|
+
}
|
|
573
|
+
Add-Check -Name "runtime_artifact_sha256" -Status "ok" -Message "WSL runtime artifact SHA256 verified"
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
Write-Step "Extracting WSL runtime artifact"
|
|
577
|
+
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
|
|
578
|
+
Expand-Archive -Path $zipPath -DestinationPath $extractDir -Force
|
|
579
|
+
$runtimeRoot = Resolve-RuntimeArtifactRoot -ExtractDir $extractDir
|
|
580
|
+
if (-not $runtimeRoot) {
|
|
581
|
+
Add-Check -Name "runtime_artifact_extract" -Status "fail" -Message "WSL runtime artifact layout not recognized"
|
|
582
|
+
Add-Step "Verify artifact contains cli-release.mjs and install-in-wsl-user.sh"
|
|
583
|
+
throw "WSL runtime artifact layout not recognized after extract."
|
|
584
|
+
}
|
|
585
|
+
$BootstrapReport.runtimeArtifact.extractedPath = $runtimeRoot
|
|
586
|
+
Add-Check -Name "runtime_artifact_extract" -Status "ok" -Message "Extracted WSL runtime artifact"
|
|
587
|
+
|
|
588
|
+
$runtimeRootWsl = Convert-WindowsPathToWslPath -WindowsPath $runtimeRoot
|
|
589
|
+
if (-not $runtimeRootWsl) {
|
|
590
|
+
Add-Check -Name "runtime_artifact_path_map" -Status "fail" -Message "Could not map runtime artifact path into WSL"
|
|
591
|
+
throw "Could not map runtime artifact path to WSL: $runtimeRoot"
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
Write-Step "Installing Cue runtime in WSL from artifact"
|
|
595
|
+
$quotedRoot = Quote-BashSingle $runtimeRootWsl
|
|
596
|
+
$installCmd = "cd $quotedRoot && chmod +x ./cli-release.mjs ./install-in-wsl-user.sh && ./install-in-wsl-user.sh"
|
|
597
|
+
$installResult = Invoke-WslCommand -Arguments @("-d", $DistroName, "--", "bash", "-lc", $installCmd)
|
|
598
|
+
$BootstrapReport.runtimeArtifact.installMode = "artifact-user-script"
|
|
599
|
+
if ($installResult.Output) {
|
|
600
|
+
$BootstrapReport.installOutput = $installResult.Output
|
|
601
|
+
if (-not $Json) {
|
|
602
|
+
Write-Host $installResult.Output
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if ($installResult.ExitCode -ne 0) {
|
|
606
|
+
Add-Check -Name "cue_install_artifact" -Status "fail" -Message "Cue runtime artifact install failed in WSL"
|
|
607
|
+
Add-Step "Run the installer script manually inside WSL to inspect errors"
|
|
608
|
+
throw "Cue runtime artifact install failed in WSL."
|
|
609
|
+
}
|
|
610
|
+
Add-Check -Name "cue_install_artifact" -Status "ok" -Message "Cue runtime artifact installed in WSL"
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function Get-WslCuePath {
|
|
614
|
+
param([Parameter(Mandatory = $true)][string]$DistroName)
|
|
615
|
+
|
|
616
|
+
$cuePathResult = Invoke-WslCommand -Arguments @(
|
|
617
|
+
"-d", $DistroName, "--", "bash", "-lc",
|
|
618
|
+
'if [ -d "$HOME/.cue/bin" ]; then export PATH="$HOME/.cue/bin:$PATH"; fi; command -v cue || true'
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if ($cuePathResult.ExitCode -ne 0) {
|
|
622
|
+
return $null
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
$cuePath = $cuePathResult.Output.Trim()
|
|
626
|
+
if ([string]::IsNullOrWhiteSpace($cuePath)) {
|
|
627
|
+
return $null
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return $cuePath
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function Test-IsWindowsShimPath {
|
|
634
|
+
param([string]$Value)
|
|
635
|
+
if ([string]::IsNullOrWhiteSpace($Value)) {
|
|
636
|
+
return $false
|
|
637
|
+
}
|
|
638
|
+
return ($Value -match '^/mnt/' -or $Value -match '\.(exe|cmd)$')
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function Get-WslUserCuePath {
|
|
642
|
+
return '$HOME/.cue/bin/cue'
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function Get-HostPackageVersion {
|
|
646
|
+
$candidatePaths = @(
|
|
647
|
+
(Join-Path $PSScriptRoot "..\package.json"),
|
|
648
|
+
(Join-Path $PSScriptRoot "..\..\package.json")
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
foreach ($candidate in $candidatePaths) {
|
|
652
|
+
try {
|
|
653
|
+
if (-not (Test-Path -LiteralPath $candidate)) {
|
|
654
|
+
continue
|
|
655
|
+
}
|
|
656
|
+
$raw = Get-Content -LiteralPath $candidate -Raw
|
|
657
|
+
$pkg = $raw | ConvertFrom-Json
|
|
658
|
+
if ($pkg -and $pkg.version) {
|
|
659
|
+
return [string]$pkg.version
|
|
660
|
+
}
|
|
661
|
+
} catch {
|
|
662
|
+
continue
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return $null
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function Get-CueSemver {
|
|
670
|
+
param([string]$VersionText)
|
|
671
|
+
if ([string]::IsNullOrWhiteSpace($VersionText)) {
|
|
672
|
+
return $null
|
|
673
|
+
}
|
|
674
|
+
$match = [regex]::Match($VersionText, '\b(\d+\.\d+\.\d+)\b')
|
|
675
|
+
if (-not $match.Success) {
|
|
676
|
+
return $null
|
|
677
|
+
}
|
|
678
|
+
return $match.Groups[1].Value
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function Add-CueVersionCompatibilityChecks {
|
|
682
|
+
param(
|
|
683
|
+
[string]$HostVersionText,
|
|
684
|
+
[string]$WslCueVersionText,
|
|
685
|
+
[switch]$AfterInstall
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
if ([string]::IsNullOrWhiteSpace($HostVersionText)) {
|
|
689
|
+
Add-Check -Name "cue_version_compat" -Status "warn" -Message "Could not compare host/WSL versions because host package version is unavailable"
|
|
690
|
+
return
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if ([string]::IsNullOrWhiteSpace($WslCueVersionText)) {
|
|
694
|
+
Add-Check -Name "cue_version_compat" -Status "warn" -Message "Could not compare host/WSL versions because WSL Cue version is unavailable"
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
$hostSemver = Get-CueSemver $HostVersionText
|
|
699
|
+
$wslSemver = Get-CueSemver $WslCueVersionText
|
|
700
|
+
if ([string]::IsNullOrWhiteSpace($hostSemver) -or [string]::IsNullOrWhiteSpace($wslSemver)) {
|
|
701
|
+
Add-Check -Name "cue_version_compat" -Status "warn" -Message "Could not parse host/WSL Cue versions for compatibility check"
|
|
702
|
+
return
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
$BootstrapReport.host.version = $HostVersionText
|
|
706
|
+
$BootstrapReport.cue.hostVersion = $HostVersionText
|
|
707
|
+
$BootstrapReport.cue.hostSemver = $hostSemver
|
|
708
|
+
$BootstrapReport.cue.wslSemver = $wslSemver
|
|
709
|
+
|
|
710
|
+
if ($hostSemver -eq $wslSemver) {
|
|
711
|
+
Add-Check -Name "cue_version_compat" -Status "ok" -Message "Host and WSL Cue versions match ($hostSemver)"
|
|
712
|
+
return
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if ($AfterInstall) {
|
|
716
|
+
Add-Check -Name "cue_version_compat" -Status "warn" -Message "Host and WSL Cue versions differ after install (host $hostSemver, WSL $wslSemver)"
|
|
717
|
+
Add-Step "Rerun bootstrap with -InstallCue to refresh WSL runtime, or update the Windows host package"
|
|
718
|
+
} else {
|
|
719
|
+
Add-Check -Name "cue_version_compat" -Status "warn" -Message "Host and WSL Cue versions differ (host $hostSemver, WSL $wslSemver)"
|
|
720
|
+
Add-Step "Run 'cue --windows-bootstrap --install-cue' to align the WSL runtime with the host package"
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
Write-Step "Cue Windows (WSL2) bootstrap checks"
|
|
725
|
+
|
|
726
|
+
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
|
|
727
|
+
Add-Check -Name "wsl_exe" -Status "fail" -Message "wsl.exe not found"
|
|
728
|
+
Add-Step "Install WSL2 first, then rerun bootstrap"
|
|
729
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
730
|
+
Write-Error "wsl.exe not found. Install WSL2 first, then rerun."
|
|
731
|
+
}
|
|
732
|
+
Add-Check -Name "wsl_exe" -Status "ok" -Message "wsl.exe found"
|
|
733
|
+
|
|
734
|
+
$wslStatus = Invoke-WslCommandRawCapture -Arguments @("--status")
|
|
735
|
+
if ($wslStatus.ExitCode -ne 0) {
|
|
736
|
+
Add-Check -Name "wsl_status" -Status "warn" -Message "wsl --status returned non-zero"
|
|
737
|
+
Write-WarnMessage "wsl --status returned non-zero. Continuing with additional checks."
|
|
738
|
+
} else {
|
|
739
|
+
Add-Check -Name "wsl_status" -Status "ok" -Message "wsl --status succeeded"
|
|
740
|
+
}
|
|
741
|
+
if ($wslStatus.Output) {
|
|
742
|
+
$BootstrapReport.wslStatus = $wslStatus.Output
|
|
743
|
+
if (-not $Json) {
|
|
744
|
+
Write-Host $wslStatus.Output
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
Write-Step "Checking WSL distros"
|
|
749
|
+
$distroListResult = Invoke-WslCommandRawCapture -Arguments @("-l", "-q")
|
|
750
|
+
if ($distroListResult.ExitCode -ne 0) {
|
|
751
|
+
$detail = $distroListResult.Output
|
|
752
|
+
Add-Check -Name "wsl_distro_list" -Status "fail" -Message "Failed to list WSL distros"
|
|
753
|
+
Add-Step "Run 'wsl -l -q' in PowerShell to diagnose WSL installation"
|
|
754
|
+
$BootstrapReport.wslDistroListError = $detail
|
|
755
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
756
|
+
Write-Error "Failed to list WSL distros. Output:`n$detail"
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
$distros = @()
|
|
760
|
+
foreach ($line in ($distroListResult.Output -split "(`r`n|`n|`r)")) {
|
|
761
|
+
$trimmed = Normalize-WslOutputLine $line
|
|
762
|
+
if ($trimmed) {
|
|
763
|
+
$distros += $trimmed
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
$BootstrapReport.installedDistros = $distros
|
|
767
|
+
|
|
768
|
+
if ($distros.Count -eq 0) {
|
|
769
|
+
Add-Check -Name "wsl_distro_list" -Status "fail" -Message "No WSL distro installed"
|
|
770
|
+
Write-WarnMessage "No WSL distro is installed."
|
|
771
|
+
Add-Step "Install a distro (recommended): wsl --install -d $Distro"
|
|
772
|
+
if (-not $Json) {
|
|
773
|
+
Write-Host "Install one (recommended):"
|
|
774
|
+
Write-Host " wsl --install -d $Distro"
|
|
775
|
+
}
|
|
776
|
+
Complete-Bootstrap -ExitCode 1 -Ok $false
|
|
777
|
+
}
|
|
778
|
+
Add-Check -Name "wsl_distro_list" -Status "ok" -Message "Found $($distros.Count) installed distro(s)"
|
|
779
|
+
|
|
780
|
+
if (-not $Json) {
|
|
781
|
+
Write-Host "Installed distros:"
|
|
782
|
+
foreach ($name in $distros) {
|
|
783
|
+
Write-Host " - $name"
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (-not ($distros -contains $Distro)) {
|
|
788
|
+
Add-Check -Name "supported_distro" -Status "fail" -Message "Supported distro '$Distro' not found"
|
|
789
|
+
Write-WarnMessage "Supported distro '$Distro' not found."
|
|
790
|
+
Add-Step "Install recommended distro: wsl --install -d $Distro"
|
|
791
|
+
if (-not $Json) {
|
|
792
|
+
Write-Host "Recommended install command:"
|
|
793
|
+
Write-Host " wsl --install -d $Distro"
|
|
794
|
+
}
|
|
795
|
+
Complete-Bootstrap -ExitCode 1 -Ok $false
|
|
796
|
+
}
|
|
797
|
+
Add-Check -Name "supported_distro" -Status "ok" -Message "Found supported distro: $Distro"
|
|
798
|
+
Write-Ok "Found supported distro: $Distro"
|
|
799
|
+
|
|
800
|
+
Write-Step "Checking bash in WSL"
|
|
801
|
+
$bashCheck = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "command -v bash >/dev/null && echo bash-ok")
|
|
802
|
+
if ($bashCheck.ExitCode -ne 0 -or -not ($bashCheck.Output -match "bash-ok")) {
|
|
803
|
+
Add-Check -Name "bash" -Status "fail" -Message "bash not available in WSL distro '$Distro'"
|
|
804
|
+
Add-Step "Open WSL distro '$Distro' and ensure bash is installed/runnable"
|
|
805
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
806
|
+
Write-Error "bash not available in WSL distro '$Distro'."
|
|
807
|
+
}
|
|
808
|
+
Add-Check -Name "bash" -Status "ok" -Message "bash available in WSL distro '$Distro'"
|
|
809
|
+
Write-Ok "bash available"
|
|
810
|
+
|
|
811
|
+
if ($RequireSystemd) {
|
|
812
|
+
Write-Step "Checking systemd"
|
|
813
|
+
$systemdCheck = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "test -d /run/systemd/system && echo systemd-ok")
|
|
814
|
+
if ($systemdCheck.ExitCode -ne 0 -or -not ($systemdCheck.Output -match "systemd-ok")) {
|
|
815
|
+
Add-Check -Name "systemd" -Status "warn" -Message "systemd not detected in '$Distro'"
|
|
816
|
+
Write-WarnMessage "systemd does not appear to be enabled in '$Distro'."
|
|
817
|
+
Add-Step "Enable systemd in WSL ($Distro): edit /etc/wsl.conf with [boot] systemd=true"
|
|
818
|
+
Add-Step "Run from PowerShell: wsl --shutdown"
|
|
819
|
+
Add-Step "Reopen WSL and verify: systemctl --user status"
|
|
820
|
+
if (-not $Json) {
|
|
821
|
+
Write-Host "Cue manager/service parity may be incomplete until systemd is enabled in WSL."
|
|
822
|
+
Write-Host "See docs/windows/support.md for supported environment requirements."
|
|
823
|
+
}
|
|
824
|
+
} else {
|
|
825
|
+
Add-Check -Name "systemd" -Status "ok" -Message "systemd enabled in '$Distro'"
|
|
826
|
+
Write-Ok "systemd enabled"
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
Write-Step "Checking runtime (bun or npm) in WSL"
|
|
831
|
+
$nodePathCheck = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "command -v node || true")
|
|
832
|
+
if ($nodePathCheck.ExitCode -ne 0) {
|
|
833
|
+
Add-Check -Name "node" -Status "fail" -Message "Failed to check node runtime in WSL distro '$Distro'"
|
|
834
|
+
Add-Step "Open WSL and verify Linux Node.js is installed"
|
|
835
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
836
|
+
Write-Error "Failed to check node runtime in WSL distro '$Distro'."
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
$nodePath = $nodePathCheck.Output.Trim()
|
|
840
|
+
if ([string]::IsNullOrWhiteSpace($nodePath)) {
|
|
841
|
+
Add-Check -Name "node" -Status "fail" -Message "Linux node runtime not found in WSL"
|
|
842
|
+
Write-WarnMessage "Node.js is not installed inside WSL (Linux runtime required to run Cue)."
|
|
843
|
+
Add-Step "Install Linux Node.js/npm in Ubuntu (example): sudo apt update && sudo apt install -y nodejs npm"
|
|
844
|
+
Add-Step "Or install Bun and ensure Linux node is available before rerunning bootstrap"
|
|
845
|
+
if (-not $Json) {
|
|
846
|
+
Write-Host "Install Linux Node.js inside WSL, then rerun bootstrap."
|
|
847
|
+
}
|
|
848
|
+
Complete-Bootstrap -ExitCode 1 -Ok $false
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if ($nodePath -match '^/mnt/' -or $nodePath -match '\.exe$') {
|
|
852
|
+
Add-Check -Name "node" -Status "fail" -Message "WSL node resolves to Windows path: $nodePath"
|
|
853
|
+
Write-WarnMessage "WSL node resolves to a Windows executable. Cue requires a Linux Node.js runtime inside WSL."
|
|
854
|
+
Add-Step "Install Linux Node.js/npm in Ubuntu (example): sudo apt update && sudo apt install -y nodejs npm"
|
|
855
|
+
Add-Step "Ensure 'command -v node' returns a Linux path (e.g. /usr/bin/node), then rerun bootstrap"
|
|
856
|
+
if (-not $Json) {
|
|
857
|
+
Write-Host "WSL node currently resolves to a Windows path:"
|
|
858
|
+
Write-Host " $nodePath"
|
|
859
|
+
}
|
|
860
|
+
Complete-Bootstrap -ExitCode 1 -Ok $false
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
Add-Check -Name "node" -Status "ok" -Message "Linux node runtime found in WSL: $nodePath"
|
|
864
|
+
Write-Ok "Linux node runtime found"
|
|
865
|
+
|
|
866
|
+
$runtimeCheck = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "if command -v bun >/dev/null; then echo bun; elif command -v npm >/dev/null; then echo npm; else echo none; fi")
|
|
867
|
+
if ($runtimeCheck.ExitCode -ne 0) {
|
|
868
|
+
Add-Check -Name "runtime" -Status "fail" -Message "Failed to check runtime in WSL distro '$Distro'"
|
|
869
|
+
Add-Step "Open WSL and verify Bun or Node/npm is installed"
|
|
870
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
871
|
+
Write-Error "Failed to check runtime in WSL distro '$Distro'."
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
$runtime = $runtimeCheck.Output.Trim()
|
|
875
|
+
$BootstrapReport.runtime = $runtime
|
|
876
|
+
if ($runtime -eq "none" -or [string]::IsNullOrWhiteSpace($runtime)) {
|
|
877
|
+
Add-Check -Name "runtime" -Status "fail" -Message "Neither bun nor npm found in WSL"
|
|
878
|
+
Write-WarnMessage "Neither bun nor npm was found in WSL."
|
|
879
|
+
Add-Step "Install Bun (recommended) or Node/npm inside WSL, then rerun bootstrap"
|
|
880
|
+
Add-Step "Bun install guide: https://bun.sh/"
|
|
881
|
+
if (-not $Json) {
|
|
882
|
+
Write-Host "Install Bun (recommended) or Node/npm inside WSL, then rerun."
|
|
883
|
+
Write-Host " Bun: https://bun.sh/"
|
|
884
|
+
}
|
|
885
|
+
Complete-Bootstrap -ExitCode 1 -Ok $false
|
|
886
|
+
}
|
|
887
|
+
Add-Check -Name "runtime" -Status "ok" -Message "Found runtime in WSL: $runtime"
|
|
888
|
+
Write-Ok "Found runtime in WSL: $runtime"
|
|
889
|
+
|
|
890
|
+
$hostVersion = Get-HostPackageVersion
|
|
891
|
+
if ($hostVersion) {
|
|
892
|
+
$BootstrapReport.host.version = $hostVersion
|
|
893
|
+
Add-Check -Name "host_version" -Status "ok" -Message "Host Cue package version: $hostVersion"
|
|
894
|
+
} else {
|
|
895
|
+
Add-Check -Name "host_version" -Status "warn" -Message "Could not detect host Cue package version"
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
$resolvedRuntimeArtifact = $null
|
|
899
|
+
if (-not [string]::IsNullOrWhiteSpace($RuntimeUrl)) {
|
|
900
|
+
$resolvedArgSha256 = $null
|
|
901
|
+
if (-not [string]::IsNullOrWhiteSpace($RuntimeSha256)) {
|
|
902
|
+
$resolvedArgSha256 = $RuntimeSha256
|
|
903
|
+
}
|
|
904
|
+
$resolvedRuntimeArtifact = [pscustomobject]@{
|
|
905
|
+
Url = $RuntimeUrl
|
|
906
|
+
Sha256 = $resolvedArgSha256
|
|
907
|
+
Source = "argument"
|
|
908
|
+
}
|
|
909
|
+
} elseif ($InstallCue) {
|
|
910
|
+
$resolvedRuntimeArtifact = Resolve-ConfiguredRuntimeArtifactSource
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if ($resolvedRuntimeArtifact) {
|
|
914
|
+
$RuntimeUrl = [string]$resolvedRuntimeArtifact.Url
|
|
915
|
+
if ([string]::IsNullOrWhiteSpace($RuntimeSha256) -and $resolvedRuntimeArtifact.Sha256) {
|
|
916
|
+
$RuntimeSha256 = [string]$resolvedRuntimeArtifact.Sha256
|
|
917
|
+
}
|
|
918
|
+
$BootstrapReport.runtimeArtifact.url = $RuntimeUrl
|
|
919
|
+
if ($RuntimeSha256) {
|
|
920
|
+
$BootstrapReport.runtimeArtifact.sha256 = $RuntimeSha256
|
|
921
|
+
}
|
|
922
|
+
$BootstrapReport.runtimeArtifact.source = [string]$resolvedRuntimeArtifact.Source
|
|
923
|
+
Add-Check -Name "runtime_artifact_source" -Status "ok" -Message "Resolved runtime artifact source ($($resolvedRuntimeArtifact.Source))"
|
|
924
|
+
if (-not $Json) {
|
|
925
|
+
Write-Host "Resolved runtime artifact source ($($resolvedRuntimeArtifact.Source)): $RuntimeUrl"
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
Write-Step "Checking Cue in WSL"
|
|
930
|
+
$userCuePath = Get-WslUserCuePath
|
|
931
|
+
$userCueVersionProbe = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", 'if [ -e "$HOME/.cue/bin/cue" ]; then "$HOME/.cue/bin/cue" --version; else exit 3; fi')
|
|
932
|
+
$cueUserPathRunnable = $false
|
|
933
|
+
$cueVersionText = $null
|
|
934
|
+
if ($userCueVersionProbe.ExitCode -eq 0) {
|
|
935
|
+
$cueUserPathRunnable = $true
|
|
936
|
+
if (-not [string]::IsNullOrWhiteSpace($userCueVersionProbe.Output)) {
|
|
937
|
+
$cueVersionText = $userCueVersionProbe.Output.Trim()
|
|
938
|
+
}
|
|
939
|
+
Add-Check -Name "cue_user_path" -Status "ok" -Message "Cue runtime found at ~/.cue/bin/cue"
|
|
940
|
+
} else {
|
|
941
|
+
$userCueNodeProbe = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", 'if [ -e "$HOME/.cue/bin/cue" ]; then node "$HOME/.cue/bin/cue" --version; else exit 3; fi')
|
|
942
|
+
if ($userCueNodeProbe.ExitCode -eq 0) {
|
|
943
|
+
$cueUserPathRunnable = $true
|
|
944
|
+
if (-not [string]::IsNullOrWhiteSpace($userCueNodeProbe.Output)) {
|
|
945
|
+
$cueVersionText = $userCueNodeProbe.Output.Trim()
|
|
946
|
+
}
|
|
947
|
+
Add-Check -Name "cue_user_path" -Status "ok" -Message "Cue runtime found at ~/.cue/bin/cue (node fallback)"
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
$cuePath = Get-WslCuePath -DistroName $Distro
|
|
952
|
+
$cuePathIsWindowsShim = Test-IsWindowsShimPath -Value $cuePath
|
|
953
|
+
if ($cuePath) {
|
|
954
|
+
if ($cuePathIsWindowsShim) {
|
|
955
|
+
Add-Check -Name "cue_path" -Status "warn" -Message "WSL cue resolves to a Windows command: $cuePath"
|
|
956
|
+
Write-WarnMessage "WSL cue resolves to a Windows command: $cuePath"
|
|
957
|
+
Add-Step "Install Linux Cue in WSL or use bootstrap runtime artifact install; prefer ~/.cue/bin on PATH"
|
|
958
|
+
} else {
|
|
959
|
+
Add-Check -Name "cue_path" -Status "ok" -Message "Linux cue found in WSL: $cuePath"
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if ([string]::IsNullOrWhiteSpace($cueVersionText) -and $cuePath -and -not $cuePathIsWindowsShim) {
|
|
964
|
+
$cueVersion = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", 'export PATH="$HOME/.cue/bin:$PATH"; cue --version')
|
|
965
|
+
if ($cueVersion.ExitCode -eq 0 -and -not [string]::IsNullOrWhiteSpace($cueVersion.Output)) {
|
|
966
|
+
$cueVersionText = $cueVersion.Output.Trim()
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
$shouldInstallFromArtifact = [bool]$InstallCue -and (-not [string]::IsNullOrWhiteSpace($RuntimeUrl))
|
|
971
|
+
if ($cueUserPathRunnable -or -not [string]::IsNullOrWhiteSpace($cueVersionText)) {
|
|
972
|
+
if ($shouldInstallFromArtifact) {
|
|
973
|
+
Add-Check -Name "cue" -Status "ok" -Message "Cue already installed in WSL (will install/update from runtime artifact)"
|
|
974
|
+
} else {
|
|
975
|
+
if ([string]::IsNullOrWhiteSpace($cueVersionText)) {
|
|
976
|
+
Add-Check -Name "cue" -Status "ok" -Message "Cue already installed in WSL (version text unavailable)"
|
|
977
|
+
} else {
|
|
978
|
+
Add-Check -Name "cue" -Status "ok" -Message "Cue already installed in WSL"
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
$BootstrapReport.cue.installed = $true
|
|
982
|
+
$BootstrapReport.cue.version = $cueVersionText
|
|
983
|
+
Add-CueVersionCompatibilityChecks -HostVersionText $hostVersion -WslCueVersionText $cueVersionText
|
|
984
|
+
if ($shouldInstallFromArtifact) {
|
|
985
|
+
$BootstrapReport.cue.previousVersion = $cueVersionText
|
|
986
|
+
Write-Ok "Cue already installed in WSL (runtime artifact install/update requested)"
|
|
987
|
+
Add-Step "Proceeding with runtime artifact install/update because -InstallCue and a runtime artifact source are available"
|
|
988
|
+
if (-not $Json) {
|
|
989
|
+
Write-Host "Proceeding with runtime artifact install/update..."
|
|
990
|
+
}
|
|
991
|
+
} else {
|
|
992
|
+
Write-Ok "Cue already installed in WSL"
|
|
993
|
+
}
|
|
994
|
+
if (-not $Json -and $cueVersionText) {
|
|
995
|
+
Write-Host $cueVersionText
|
|
996
|
+
}
|
|
997
|
+
if (-not $shouldInstallFromArtifact) {
|
|
998
|
+
Complete-Bootstrap -ExitCode 0 -Ok $true
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (-not $InstallCue) {
|
|
1003
|
+
Add-Check -Name "cue" -Status "fail" -Message "Cue not installed in WSL"
|
|
1004
|
+
Write-WarnMessage "Cue is not installed in WSL yet."
|
|
1005
|
+
Add-Step "Install Cue inside WSL and rerun bootstrap"
|
|
1006
|
+
if ($RuntimeUrl) {
|
|
1007
|
+
Add-Step "Or rerun bootstrap with -InstallCue (runtime artifact source is already configured)"
|
|
1008
|
+
Add-Step "Or rerun bootstrap with -InstallCue -RuntimeUrl <artifact-url>"
|
|
1009
|
+
if (-not $Json) {
|
|
1010
|
+
Write-Host "Install via runtime artifact:"
|
|
1011
|
+
Write-Host " cue --windows-bootstrap --install-cue"
|
|
1012
|
+
Write-Host " # or explicit override:"
|
|
1013
|
+
Write-Host " cue --windows-bootstrap --install-cue --runtime-url <artifact-url>"
|
|
1014
|
+
Write-Host ""
|
|
1015
|
+
}
|
|
1016
|
+
} else {
|
|
1017
|
+
Add-Step "Or rerun bootstrap with -InstallCue to auto-install using the configured runtime source (if present)"
|
|
1018
|
+
}
|
|
1019
|
+
if ($runtime -eq "bun") {
|
|
1020
|
+
$installCmd = "wsl -d $Distro -- bash -lc 'bun add -g @plusonelabs/cue'"
|
|
1021
|
+
} else {
|
|
1022
|
+
$installCmd = "wsl -d $Distro -- bash -lc 'npm install -g @plusonelabs/cue'"
|
|
1023
|
+
}
|
|
1024
|
+
Add-Step $installCmd
|
|
1025
|
+
if (-not $Json) {
|
|
1026
|
+
Write-Host "Install inside WSL and rerun:"
|
|
1027
|
+
Write-Host " $installCmd"
|
|
1028
|
+
Write-Host ""
|
|
1029
|
+
Write-Host "Or run this script with -InstallCue to install automatically."
|
|
1030
|
+
}
|
|
1031
|
+
Add-Step "Or rerun bootstrap with -InstallCue"
|
|
1032
|
+
Complete-Bootstrap -ExitCode 1 -Ok $false
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
Write-Step "Installing Cue in WSL"
|
|
1036
|
+
if ($RuntimeUrl) {
|
|
1037
|
+
try {
|
|
1038
|
+
Install-CueFromRuntimeArtifact -DistroName $Distro -ArtifactUrl $RuntimeUrl -ExpectedSha256 $RuntimeSha256
|
|
1039
|
+
} catch {
|
|
1040
|
+
if (-not $Json) {
|
|
1041
|
+
Write-Error $_
|
|
1042
|
+
}
|
|
1043
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
1044
|
+
exit 1
|
|
1045
|
+
}
|
|
1046
|
+
} else {
|
|
1047
|
+
if ($runtime -eq "bun") {
|
|
1048
|
+
$installResult = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "bun add -g @plusonelabs/cue")
|
|
1049
|
+
} else {
|
|
1050
|
+
$installResult = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "npm install -g @plusonelabs/cue")
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if ($installResult.Output) {
|
|
1054
|
+
$BootstrapReport.installOutput = $installResult.Output
|
|
1055
|
+
if (-not $Json) {
|
|
1056
|
+
Write-Host $installResult.Output
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if ($installResult.ExitCode -ne 0) {
|
|
1060
|
+
Add-Check -Name "cue_install" -Status "fail" -Message "Cue install failed in WSL"
|
|
1061
|
+
Add-Step "Run the install command manually inside WSL to inspect errors"
|
|
1062
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
1063
|
+
Write-Error "Cue install failed in WSL."
|
|
1064
|
+
}
|
|
1065
|
+
Add-Check -Name "cue_install" -Status "ok" -Message "Cue install command succeeded in WSL"
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
$userCueVersionAfter = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", "$userCuePath --version")
|
|
1069
|
+
if ($userCueVersionAfter.ExitCode -ne 0) {
|
|
1070
|
+
Add-Check -Name "cue_verify_user_path" -Status "fail" -Message "Installed Cue runtime not runnable at ~/.cue/bin/cue after install"
|
|
1071
|
+
Add-Step "Inspect ~/.cue/bin/cue and ~/.cue/runtime in WSL"
|
|
1072
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
1073
|
+
Write-Error "Installed Cue runtime not runnable at ~/.cue/bin/cue after install."
|
|
1074
|
+
}
|
|
1075
|
+
Add-Check -Name "cue_verify_user_path" -Status "ok" -Message "Installed Cue runtime runnable at ~/.cue/bin/cue"
|
|
1076
|
+
|
|
1077
|
+
$cuePathAfter = Get-WslCuePath -DistroName $Distro
|
|
1078
|
+
if ([string]::IsNullOrWhiteSpace($cuePathAfter)) {
|
|
1079
|
+
Add-Check -Name "cue_verify_path" -Status "warn" -Message "Cue not found on default WSL PATH after install"
|
|
1080
|
+
Add-Step "Add ~/.cue/bin to PATH in WSL shell rc for direct WSL terminal usage"
|
|
1081
|
+
Write-WarnMessage "Cue is installed at ~/.cue/bin/cue but not on default WSL PATH."
|
|
1082
|
+
} elseif (Test-IsWindowsShimPath -Value $cuePathAfter) {
|
|
1083
|
+
Add-Check -Name "cue_verify_path" -Status "warn" -Message "Default WSL PATH resolves cue to Windows command: $cuePathAfter"
|
|
1084
|
+
Add-Step "Add ~/.cue/bin to PATH before Windows-inherited paths in WSL for direct WSL terminal usage"
|
|
1085
|
+
Add-Step "Windows wrapper still works because it prefers ~/.cue/bin automatically"
|
|
1086
|
+
Write-WarnMessage "Default WSL PATH resolves cue to a Windows command after install: $cuePathAfter"
|
|
1087
|
+
} else {
|
|
1088
|
+
Add-Check -Name "cue_verify_path" -Status "ok" -Message "Default WSL PATH resolves cue to Linux path after install: $cuePathAfter"
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
$cueVersionAfter = Invoke-WslCommand -Arguments @("-d", $Distro, "--", "bash", "-lc", 'export PATH="$HOME/.cue/bin:$PATH"; cue --version')
|
|
1092
|
+
if ($cueVersionAfter.ExitCode -ne 0) {
|
|
1093
|
+
Add-Check -Name "cue_verify" -Status "fail" -Message "Cue not runnable in WSL after install"
|
|
1094
|
+
if ($Json) { Complete-Bootstrap -ExitCode 1 -Ok $false }
|
|
1095
|
+
Write-Error "Cue still not runnable in WSL after install."
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
Add-Check -Name "cue_verify" -Status "ok" -Message "Cue installed and runnable in WSL"
|
|
1099
|
+
$BootstrapReport.cue.installed = $true
|
|
1100
|
+
$resolvedInstalledCueVersion = $cueVersionAfter.Output.Trim()
|
|
1101
|
+
if ([string]::IsNullOrWhiteSpace($resolvedInstalledCueVersion) -and -not [string]::IsNullOrWhiteSpace($userCueVersionAfter.Output)) {
|
|
1102
|
+
$resolvedInstalledCueVersion = $userCueVersionAfter.Output.Trim()
|
|
1103
|
+
}
|
|
1104
|
+
$BootstrapReport.cue.version = $resolvedInstalledCueVersion
|
|
1105
|
+
Add-CueVersionCompatibilityChecks -HostVersionText $hostVersion -WslCueVersionText $resolvedInstalledCueVersion -AfterInstall
|
|
1106
|
+
Write-Ok "Cue installed and runnable in WSL"
|
|
1107
|
+
Add-Step "Next: run 'cue' from Windows (wrapper) or inside WSL"
|
|
1108
|
+
if (-not $Json) {
|
|
1109
|
+
Write-Host $cueVersionAfter.Output
|
|
1110
|
+
Write-Host ""
|
|
1111
|
+
Write-Host "Next: run 'cue' from Windows (wrapper) or inside WSL."
|
|
1112
|
+
}
|
|
1113
|
+
Complete-Bootstrap -ExitCode 0 -Ok $true
|