@mriekki/project-forge 4.13.5 → 4.14.0
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
CHANGED
|
@@ -94,6 +94,8 @@ clone repository
|
|
|
94
94
|
|
|
95
95
|
Project Forge supports independent workspace instances on multiple developer computers. Git/GitHub is the shared source boundary; local databases, credentials, SSH keys/trust, Docker state, and readiness stay workstation-local.
|
|
96
96
|
|
|
97
|
+
On Windows, Project Forge defaults compatible development to **WSL2 with Ubuntu**: keep repositories in the WSL Linux filesystem (normally `~/projects`), run shells/toolchains from WSL, keep desktop UI applications on Windows, and use Docker Desktop with its WSL2 backend/integration rather than installing a second Docker Engine inside the distro. Native Windows remains valid for Projects with a real Windows-specific requirement. Existing Windows checkouts can be migrated non-destructively with [`tools/windows/project-forge-wsl2.ps1`](tools/windows/project-forge-wsl2.ps1); see [`docs/WINDOWS_WSL2.md`](docs/WINDOWS_WSL2.md).
|
|
98
|
+
|
|
97
99
|
## Public ChatGPT contract
|
|
98
100
|
|
|
99
101
|
Project Forge's public ChatGPT bootstrap entrypoint is [`chatgpt/START_HERE.md`](chatgpt/START_HERE.md).
|
package/assets/components.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mriekki/project-forge",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.14.0",
|
|
4
4
|
"description": "Public consumer distribution and updater for Project Forge engineering assets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"src/",
|
|
12
12
|
"assets/",
|
|
13
|
+
"tools/",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
2
|
+
param(
|
|
3
|
+
[string]$Distro = "Ubuntu",
|
|
4
|
+
[string]$SourceRoot = (Get-Location).Path,
|
|
5
|
+
[string]$DestinationRoot = "~/projects",
|
|
6
|
+
[switch]$InstallMissing,
|
|
7
|
+
[switch]$MigrateRepositories,
|
|
8
|
+
[switch]$IncludeNestedRepositories,
|
|
9
|
+
[switch]$SkipDockerCheck
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
$ErrorActionPreference = "Stop"
|
|
13
|
+
|
|
14
|
+
function Write-Step([string]$Message) {
|
|
15
|
+
Write-Host "==> $Message"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Invoke-Wsl {
|
|
19
|
+
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$Arguments)
|
|
20
|
+
$output = & wsl.exe -d $Distro -- @Arguments 2>&1
|
|
21
|
+
if ($LASTEXITCODE -ne 0) {
|
|
22
|
+
throw "WSL command failed ($LASTEXITCODE): $($Arguments -join ' ')`n$output"
|
|
23
|
+
}
|
|
24
|
+
return $output
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Test-Command([string]$Name) {
|
|
28
|
+
return $null -ne (Get-Command $Name -ErrorAction SilentlyContinue)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function Get-GitValue([string]$Repository, [string[]]$Arguments) {
|
|
32
|
+
$result = & git -C $Repository @Arguments 2>$null
|
|
33
|
+
if ($LASTEXITCODE -ne 0) { return $null }
|
|
34
|
+
return (($result -join "`n").Trim())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Convert-ToWslPath([string]$WindowsPath) {
|
|
38
|
+
return ((Invoke-Wsl wslpath -a $WindowsPath) -join "`n").Trim()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function Resolve-WslDestinationRoot {
|
|
42
|
+
$home = ((Invoke-Wsl sh -lc 'printf %s "$HOME"') -join "`n").Trim()
|
|
43
|
+
if ($DestinationRoot -eq "~") { return $home }
|
|
44
|
+
if ($DestinationRoot.StartsWith("~/")) {
|
|
45
|
+
return "$home/$($DestinationRoot.Substring(2))"
|
|
46
|
+
}
|
|
47
|
+
if ($DestinationRoot.StartsWith("/")) { return $DestinationRoot }
|
|
48
|
+
throw "DestinationRoot must be an absolute Linux path or start with ~/ (received '$DestinationRoot')."
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Get-RepositoryCandidates([string]$Root) {
|
|
52
|
+
$resolved = (Resolve-Path $Root).Path
|
|
53
|
+
$items = New-Object System.Collections.Generic.List[string]
|
|
54
|
+
|
|
55
|
+
if (Test-Path (Join-Path $resolved ".git")) {
|
|
56
|
+
$items.Add($resolved)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$children = Get-ChildItem -Path $resolved -Directory -Force
|
|
60
|
+
foreach ($child in $children) {
|
|
61
|
+
if (Test-Path (Join-Path $child.FullName ".git")) {
|
|
62
|
+
$items.Add($child.FullName)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if ($IncludeNestedRepositories) {
|
|
67
|
+
Get-ChildItem -Path $resolved -Directory -Force -Recurse -ErrorAction SilentlyContinue |
|
|
68
|
+
Where-Object { Test-Path (Join-Path $_.FullName ".git") } |
|
|
69
|
+
ForEach-Object { $items.Add($_.FullName) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return $items | Sort-Object -Unique
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (-not [System.Environment]::OSVersion.Platform.ToString().StartsWith("Win")) {
|
|
76
|
+
throw "This tool must be run from Windows PowerShell or PowerShell on Windows."
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Write-Step "Checking WSL2"
|
|
80
|
+
if (-not (Test-Command "wsl.exe")) {
|
|
81
|
+
if (-not $InstallMissing) {
|
|
82
|
+
throw "WSL is not available. Re-run with -InstallMissing from an elevated PowerShell session."
|
|
83
|
+
}
|
|
84
|
+
& wsl.exe --install -d $Distro
|
|
85
|
+
throw "WSL installation was requested. If Windows asks for a restart, restart first, complete the $Distro first-run setup, then run this command again."
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
& wsl.exe --set-default-version 2 | Out-Null
|
|
89
|
+
$distroNames = (& wsl.exe -l -q 2>$null) -replace "`0", "" | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
90
|
+
if ($distroNames -notcontains $Distro) {
|
|
91
|
+
if (-not $InstallMissing) {
|
|
92
|
+
throw "WSL distro '$Distro' is not installed. Re-run with -InstallMissing."
|
|
93
|
+
}
|
|
94
|
+
& wsl.exe --install -d $Distro
|
|
95
|
+
throw "Installation of '$Distro' was requested. Complete its first-run setup, then run this command again."
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
$versionLines = (& wsl.exe -l -v 2>$null) -replace "`0", ""
|
|
99
|
+
$distroLine = $versionLines | Where-Object { $_ -match "\b$([regex]::Escape($Distro))\b" } | Select-Object -First 1
|
|
100
|
+
if ($distroLine -and $distroLine -match "\s1\s*$") {
|
|
101
|
+
if (-not $InstallMissing) {
|
|
102
|
+
throw "'$Distro' is using WSL1. Re-run with -InstallMissing to convert it to WSL2."
|
|
103
|
+
}
|
|
104
|
+
& wsl.exe --set-version $Distro 2
|
|
105
|
+
if ($LASTEXITCODE -ne 0) { throw "Failed to convert '$Distro' to WSL2." }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Write-Step "Checking baseline Linux development tools"
|
|
109
|
+
$missingLinuxTools = @()
|
|
110
|
+
foreach ($tool in @("git", "curl", "tar")) {
|
|
111
|
+
& wsl.exe -d $Distro -- sh -lc "command -v $tool >/dev/null 2>&1"
|
|
112
|
+
if ($LASTEXITCODE -ne 0) { $missingLinuxTools += $tool }
|
|
113
|
+
}
|
|
114
|
+
if ($missingLinuxTools.Count -gt 0) {
|
|
115
|
+
if (-not $InstallMissing) {
|
|
116
|
+
throw "Missing WSL tools: $($missingLinuxTools -join ', '). Re-run with -InstallMissing."
|
|
117
|
+
}
|
|
118
|
+
Invoke-Wsl sudo apt-get update | Out-Null
|
|
119
|
+
Invoke-Wsl sudo apt-get install -y git curl ca-certificates tar build-essential | Out-Null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Use Linux line endings for new checkouts unless the Builder already chose another global policy.
|
|
123
|
+
$wslAutocrlf = (& wsl.exe -d $Distro -- git config --global --get core.autocrlf 2>$null | Out-String).Trim()
|
|
124
|
+
if (-not $wslAutocrlf) {
|
|
125
|
+
Invoke-Wsl git config --global core.autocrlf input | Out-Null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Carry Git identity into WSL only when WSL does not already have one.
|
|
129
|
+
if (Test-Command "git") {
|
|
130
|
+
foreach ($key in @("user.name", "user.email")) {
|
|
131
|
+
$existing = (& wsl.exe -d $Distro -- git config --global --get $key 2>$null | Out-String).Trim()
|
|
132
|
+
if (-not $existing) {
|
|
133
|
+
$hostValue = (& git config --global --get $key 2>$null | Out-String).Trim()
|
|
134
|
+
if ($hostValue) { Invoke-Wsl git config --global $key $hostValue | Out-Null }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
Write-Step "Checking VS Code WSL integration"
|
|
140
|
+
if (Test-Command "code") {
|
|
141
|
+
& code --install-extension ms-vscode-remote.remote-wsl --force | Out-Null
|
|
142
|
+
} else {
|
|
143
|
+
Write-Warning "VS Code 'code' command was not found on Windows. Install VS Code if you want the recommended Windows UI + WSL workspace flow."
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Write-Step "Checking Docker Desktop / WSL integration"
|
|
147
|
+
if (-not $SkipDockerCheck) {
|
|
148
|
+
$dockerDesktop = Get-Command "docker" -ErrorAction SilentlyContinue
|
|
149
|
+
if (-not $dockerDesktop -and $InstallMissing -and (Test-Command "winget")) {
|
|
150
|
+
& winget install --id Docker.DockerDesktop -e --accept-package-agreements --accept-source-agreements
|
|
151
|
+
Write-Warning "Docker Desktop installation was requested. Start Docker Desktop, enable WSL integration for '$Distro', then rerun this command to verify Docker from WSL."
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
& wsl.exe -d $Distro -- docker info 1>$null 2>$null
|
|
155
|
+
if ($LASTEXITCODE -ne 0) {
|
|
156
|
+
Write-Warning "Docker is not operational inside '$Distro'. Start Docker Desktop and enable Settings > Resources > WSL Integration for '$Distro'. Do not install a second Docker Engine inside WSL just to satisfy this check."
|
|
157
|
+
} else {
|
|
158
|
+
Write-Host "Docker from WSL: PASS"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (-not $MigrateRepositories) {
|
|
163
|
+
Write-Host ""
|
|
164
|
+
Write-Host "WSL2 baseline setup: PASS"
|
|
165
|
+
Write-Host "No repositories were migrated. Re-run with -MigrateRepositories -SourceRoot <windows-folder> when ready."
|
|
166
|
+
exit 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (-not (Test-Command "git")) {
|
|
170
|
+
throw "Windows Git is required for migration preflight so source repositories can be verified before copying state."
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
$destinationRootLinux = Resolve-WslDestinationRoot
|
|
174
|
+
Invoke-Wsl mkdir -p $destinationRootLinux | Out-Null
|
|
175
|
+
$repositories = @(Get-RepositoryCandidates $SourceRoot)
|
|
176
|
+
if ($repositories.Count -eq 0) {
|
|
177
|
+
throw "No Git repositories found at '$SourceRoot'."
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Write-Step "Migrating $($repositories.Count) repository/repositories to $Distro:$destinationRootLinux"
|
|
181
|
+
$migrated = New-Object System.Collections.Generic.List[string]
|
|
182
|
+
$skipped = New-Object System.Collections.Generic.List[string]
|
|
183
|
+
|
|
184
|
+
foreach ($repo in $repositories) {
|
|
185
|
+
$name = Split-Path $repo -Leaf
|
|
186
|
+
$dirty = (& git -C $repo status --porcelain=v1 2>$null | Out-String).Trim()
|
|
187
|
+
if ($LASTEXITCODE -ne 0) {
|
|
188
|
+
Write-Warning "Skipping '$repo': Git status failed."
|
|
189
|
+
$skipped.Add($repo)
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
if ($dirty) {
|
|
193
|
+
Write-Warning "Skipping '$repo': working tree is not clean. Commit/stash/reconcile it first so migration cannot lose local work."
|
|
194
|
+
$skipped.Add($repo)
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
$head = Get-GitValue $repo @("rev-parse", "HEAD")
|
|
199
|
+
$branch = Get-GitValue $repo @("branch", "--show-current")
|
|
200
|
+
$origin = Get-GitValue $repo @("config", "--get", "remote.origin.url")
|
|
201
|
+
$sourceLinux = Convert-ToWslPath $repo
|
|
202
|
+
$destinationLinux = "$destinationRootLinux/$name"
|
|
203
|
+
|
|
204
|
+
& wsl.exe -d $Distro -- test -e $destinationLinux
|
|
205
|
+
if ($LASTEXITCODE -eq 0) {
|
|
206
|
+
Write-Warning "Skipping '$repo': destination '$destinationLinux' already exists."
|
|
207
|
+
$skipped.Add($repo)
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (-not $PSCmdlet.ShouldProcess($repo, "Create validated WSL2 checkout at $destinationLinux")) {
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Clone from the local clean repository so committed local-only refs remain available during migration.
|
|
216
|
+
Invoke-Wsl git clone --no-hardlinks $sourceLinux $destinationLinux | Out-Null
|
|
217
|
+
if ($origin) {
|
|
218
|
+
Invoke-Wsl git -C $destinationLinux remote set-url origin $origin | Out-Null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
$destHead = ((Invoke-Wsl git -C $destinationLinux rev-parse HEAD) -join "`n").Trim()
|
|
222
|
+
$destDirty = ((Invoke-Wsl git -C $destinationLinux status --porcelain=v1) -join "`n").Trim()
|
|
223
|
+
if ($destHead -ne $head) {
|
|
224
|
+
throw "Migration verification failed for '$name': source HEAD $head != destination HEAD $destHead. Windows source was left untouched."
|
|
225
|
+
}
|
|
226
|
+
if ($destDirty) {
|
|
227
|
+
throw "Migration verification failed for '$name': destination checkout is not clean. Windows source was left untouched."
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if ($branch) {
|
|
231
|
+
$destBranch = ((Invoke-Wsl git -C $destinationLinux branch --show-current) -join "`n").Trim()
|
|
232
|
+
if ($destBranch -ne $branch) {
|
|
233
|
+
Write-Warning "'$name' migrated at the correct HEAD, but checked-out branch differs (source '$branch', destination '$destBranch')."
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Write-Host "Migrated: $repo -> $Distro:$destinationLinux"
|
|
238
|
+
$migrated.Add($destinationLinux)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Write-Host ""
|
|
242
|
+
Write-Host "Migration complete."
|
|
243
|
+
Write-Host "Migrated: $($migrated.Count)"
|
|
244
|
+
Write-Host "Skipped: $($skipped.Count)"
|
|
245
|
+
Write-Host "Windows source checkouts were NOT deleted."
|
|
246
|
+
Write-Host ""
|
|
247
|
+
Write-Host "Next: open a migrated repository from WSL and run its normal Project Forge/project readiness checks."
|
|
248
|
+
if ($migrated.Count -gt 0) {
|
|
249
|
+
Write-Host "Example:"
|
|
250
|
+
Write-Host " wsl -d $Distro"
|
|
251
|
+
Write-Host " cd $($migrated[0])"
|
|
252
|
+
Write-Host " code ."
|
|
253
|
+
}
|