|
param( |
|
[Parameter(ValueFromRemainingArguments = $true)] |
|
[string[]] $CliArgs |
|
) |
|
|
|
$ErrorActionPreference = "Stop" |
|
|
|
$Org = if ($env:SPEC_KITTY_ORG) { $env:SPEC_KITTY_ORG } else { "Priivacy-ai" } |
|
$Base = if ($env:SPEC_KITTY_DEV_DIR) { $env:SPEC_KITTY_DEV_DIR } else { Join-Path $HOME "spec-kitty-dev" } |
|
|
|
$Repos = @( |
|
"spec-kitty", |
|
"spec-kitty-saas", |
|
"spec-kitty-tracker", |
|
"spec-kitty-events", |
|
"spec-kitty-runtime", |
|
"spec-kitty-orchestrator", |
|
"spec-kitty-hub", |
|
"spec-kitty-website", |
|
"spec-kitty-design", |
|
"spec-kitty-planning", |
|
"spec-kitty-test", |
|
"spec-kitty-end-to-end-testing", |
|
"spec-kitty-plain-english-tests", |
|
"spec-kitty-mobile", |
|
"spec-kitty-mobile-contract-tests", |
|
"spec-kitty-mobile-android", |
|
"spec-kitty-mobile-ios", |
|
".github" |
|
) |
|
|
|
function Show-Usage { |
|
@" |
|
Usage: |
|
./make-sk-mono.ps1 |
|
./make-sk-mono.ps1 --repos <repo> [repo ...] |
|
./make-sk-mono.ps1 --all |
|
./make-sk-mono.ps1 --blank |
|
./make-sk-mono.ps1 --list |
|
./make-sk-mono.ps1 --help |
|
|
|
Interactive selector for Spec Kitty monorepo workspaces. |
|
Noninteractive modes create the workspace and print its path. |
|
Interactive mode changes the current process location to the created workspace. |
|
|
|
Keys: |
|
Up/Down or k/j Move cursor |
|
Space Toggle repo |
|
Enter Create timestamped workspace and clone selected repos |
|
a Select all |
|
c Clear selection |
|
q or Esc Quit |
|
|
|
Environment: |
|
SPEC_KITTY_DEV_DIR Workspace base dir. Default: `$HOME/spec-kitty-dev |
|
SPEC_KITTY_ORG GitHub org. Default: Priivacy-ai |
|
|
|
Output: |
|
Prints the created workspace path after cloning or blank workspace creation. |
|
"@ |
|
} |
|
|
|
function New-WorkspaceDirectory { |
|
param([string] $Prefix) |
|
|
|
New-Item -ItemType Directory -Force -Path $Base | Out-Null |
|
$Timestamp = Get-Date -Format "yyyyMMdd-HHmmss" |
|
$Suffix = [Guid]::NewGuid().ToString("N").Substring(0, 6) |
|
$Workspace = Join-Path $Base "$Prefix-$Timestamp-$Suffix" |
|
New-Item -ItemType Directory -Force -Path $Workspace | Out-Null |
|
return (Resolve-Path $Workspace).Path |
|
} |
|
|
|
function Show-Repos { |
|
for ($i = 0; $i -lt $Repos.Count; $i++) { |
|
"{0,2}) {1}" -f ($i + 1), $Repos[$i] |
|
} |
|
} |
|
|
|
function Get-RepoIndex { |
|
param([string] $Value) |
|
|
|
$Number = 0 |
|
if ([int]::TryParse($Value, [ref] $Number) -and $Number -ge 1 -and $Number -le $Repos.Count) { |
|
return $Number - 1 |
|
} |
|
|
|
for ($i = 0; $i -lt $Repos.Count; $i++) { |
|
if ($Repos[$i] -eq $Value) { |
|
return $i |
|
} |
|
} |
|
|
|
throw "Unknown repo: $Value" |
|
} |
|
|
|
function Invoke-CloneRepo { |
|
param( |
|
[string] $Repo, |
|
[string] $Destination |
|
) |
|
|
|
$Gh = Get-Command gh -ErrorAction SilentlyContinue |
|
if ($Gh) { |
|
& gh auth status *> $null |
|
if ($LASTEXITCODE -eq 0) { |
|
& gh repo clone "$Org/$Repo" "$Destination" 2>&1 | ForEach-Object { [Console]::Error.WriteLine($_) } |
|
if ($LASTEXITCODE -ne 0) { throw "Failed to clone $Org/$Repo" } |
|
return |
|
} |
|
} |
|
|
|
& git clone "git@github.com:$Org/$Repo.git" "$Destination" 2>&1 | ForEach-Object { [Console]::Error.WriteLine($_) } |
|
if ($LASTEXITCODE -ne 0) { throw "Failed to clone $Org/$Repo" } |
|
} |
|
|
|
function New-BlankWorkspace { |
|
$Workspace = New-WorkspaceDirectory -Prefix "blank" |
|
[Console]::Error.WriteLine("Created blank workspace: $Workspace") |
|
$Workspace |
|
} |
|
|
|
function New-RepoWorkspace { |
|
param([bool[]] $Checked) |
|
|
|
$Selected = @() |
|
for ($i = 0; $i -lt $Checked.Count; $i++) { |
|
if ($Checked[$i]) { $Selected += $Repos[$i] } |
|
} |
|
|
|
if ($Selected.Count -eq 0) { |
|
throw "Select at least one repo first." |
|
} |
|
|
|
$Workspace = New-WorkspaceDirectory -Prefix "spec-kitty" |
|
[Console]::Error.WriteLine("Created workspace: $Workspace") |
|
|
|
foreach ($Repo in $Selected) { |
|
[Console]::Error.WriteLine("Cloning $Org/$Repo...") |
|
Invoke-CloneRepo -Repo $Repo -Destination (Join-Path $Workspace $Repo) |
|
} |
|
|
|
$Workspace |
|
} |
|
|
|
function Select-Repos { |
|
param([string[]] $Values) |
|
|
|
$Checked = [bool[]]::new($Repos.Count) |
|
foreach ($Raw in $Values) { |
|
foreach ($Value in ($Raw -split "," | ForEach-Object { $_.Trim() } | Where-Object { $_ })) { |
|
$Checked[(Get-RepoIndex $Value)] = $true |
|
} |
|
} |
|
$Checked |
|
} |
|
|
|
function Invoke-Interactive { |
|
$Checked = [bool[]]::new($Repos.Count) |
|
$Cursor = 0 |
|
$Message = "" |
|
|
|
while ($true) { |
|
Clear-Host |
|
"Spec Kitty monorepo workspace" |
|
"Base: $Base" |
|
"" |
|
"Use arrows, Space to toggle, Enter to clone. a=all c=clear q=quit" |
|
"" |
|
|
|
for ($i = 0; $i -lt $Repos.Count; $i++) { |
|
$Pointer = if ($i -eq $Cursor) { ">" } else { " " } |
|
$Marker = if ($Checked[$i]) { "x" } else { " " } |
|
"{0} [{1}] {2,2}. {3}" -f $Pointer, $Marker, ($i + 1), $Repos[$i] |
|
} |
|
|
|
if ($Message) { |
|
"" |
|
$Message |
|
} |
|
|
|
$Key = [Console]::ReadKey($true) |
|
$Message = "" |
|
|
|
switch ($Key.Key) { |
|
"UpArrow" { $Cursor = if ($Cursor -eq 0) { $Repos.Count - 1 } else { $Cursor - 1 } } |
|
"DownArrow" { $Cursor = if ($Cursor -eq ($Repos.Count - 1)) { 0 } else { $Cursor + 1 } } |
|
"Spacebar" { $Checked[$Cursor] = -not $Checked[$Cursor] } |
|
"Enter" { |
|
try { |
|
$Workspace = New-RepoWorkspace -Checked $Checked |
|
Set-Location $Workspace |
|
$Workspace |
|
return |
|
} catch { |
|
$Message = $_.Exception.Message |
|
} |
|
} |
|
"Escape" { return } |
|
default { |
|
switch ($Key.KeyChar) { |
|
"k" { $Cursor = if ($Cursor -eq 0) { $Repos.Count - 1 } else { $Cursor - 1 } } |
|
"K" { $Cursor = if ($Cursor -eq 0) { $Repos.Count - 1 } else { $Cursor - 1 } } |
|
"j" { $Cursor = if ($Cursor -eq ($Repos.Count - 1)) { 0 } else { $Cursor + 1 } } |
|
"J" { $Cursor = if ($Cursor -eq ($Repos.Count - 1)) { 0 } else { $Cursor + 1 } } |
|
"a" { for ($i = 0; $i -lt $Checked.Count; $i++) { $Checked[$i] = $true } } |
|
"A" { for ($i = 0; $i -lt $Checked.Count; $i++) { $Checked[$i] = $true } } |
|
"c" { $Checked = [bool[]]::new($Repos.Count) } |
|
"C" { $Checked = [bool[]]::new($Repos.Count) } |
|
"q" { return } |
|
"Q" { return } |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ($CliArgs.Count -eq 0) { |
|
Invoke-Interactive |
|
exit 0 |
|
} |
|
|
|
switch ($CliArgs[0]) { |
|
{ $_ -in @("-h", "--help") } { |
|
Show-Usage |
|
exit 0 |
|
} |
|
"--list" { |
|
Show-Repos |
|
exit 0 |
|
} |
|
"--blank" { |
|
New-BlankWorkspace |
|
exit 0 |
|
} |
|
"--all" { |
|
$Checked = [bool[]]::new($Repos.Count) |
|
for ($i = 0; $i -lt $Checked.Count; $i++) { $Checked[$i] = $true } |
|
New-RepoWorkspace -Checked $Checked |
|
exit 0 |
|
} |
|
"--repos" { |
|
if ($CliArgs.Count -lt 2) { |
|
Write-Error "Missing repos after --repos." |
|
exit 2 |
|
} |
|
New-RepoWorkspace -Checked (Select-Repos -Values $CliArgs[1..($CliArgs.Count - 1)]) |
|
exit 0 |
|
} |
|
default { |
|
if ($CliArgs[0].StartsWith("--")) { |
|
Write-Error "Unknown option: $($CliArgs[0])" |
|
Show-Usage |
|
exit 2 |
|
} |
|
New-RepoWorkspace -Checked (Select-Repos -Values $CliArgs) |
|
exit 0 |
|
} |
|
} |