183 lines
6.4 KiB
PowerShell
183 lines
6.4 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
|
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$RepoRoot = Split-Path -Parent $ScriptDir
|
|
$CanonicalHome = if ($env:COPILOT_RESOURCES_HOME) { $env:COPILOT_RESOURCES_HOME } else { Join-Path $HOME '.copilot-resources' }
|
|
$StateDir = if ($env:COPILOT_RESOURCES_STATE_DIR) { $env:COPILOT_RESOURCES_STATE_DIR } else { Join-Path $HOME '.copilot-resources-state' }
|
|
$CopilotHome = if ($env:COPILOT_HOME) { $env:COPILOT_HOME } else { Join-Path $HOME '.copilot' }
|
|
$VscodeUserDir = if ($env:VSCODE_USER_DIR) { $env:VSCODE_USER_DIR } else { Join-Path $env:APPDATA 'Code\User' }
|
|
$ManagedShellEnv = $null
|
|
$ProfilePath = $PROFILE.CurrentUserAllHosts
|
|
$VscodeSettingsFile = Join-Path $VscodeUserDir 'settings.json'
|
|
|
|
function Resolve-Directory {
|
|
param([string]$Path)
|
|
(Resolve-Path -LiteralPath $Path).Path
|
|
}
|
|
|
|
function Ensure-Directory {
|
|
param([string]$Path)
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
|
}
|
|
}
|
|
|
|
function Find-NodeExecutable {
|
|
foreach ($Candidate in @('node', 'node.exe', 'nodejs', 'nodejs.exe')) {
|
|
$Command = Get-Command $Candidate -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if ($Command) {
|
|
return $Command.Source
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Quote-SingleQuoted {
|
|
param([string]$Value)
|
|
|
|
"'" + $Value.Replace("'", "''") + "'"
|
|
}
|
|
|
|
function Ensure-Junction {
|
|
param(
|
|
[string]$Target,
|
|
[string]$Path
|
|
)
|
|
|
|
$Parent = Split-Path -Parent $Path
|
|
Ensure-Directory $Parent
|
|
|
|
if (Test-Path -LiteralPath $Path) {
|
|
$Existing = Get-Item -LiteralPath $Path -Force
|
|
if ($Existing.LinkType -eq 'Junction' -or $Existing.LinkType -eq 'SymbolicLink') {
|
|
$ResolvedTarget = Resolve-Directory $Existing.Target
|
|
if ($ResolvedTarget -eq (Resolve-Directory $Target)) {
|
|
return
|
|
}
|
|
}
|
|
|
|
if ($Existing.PSIsContainer -and -not (Get-ChildItem -LiteralPath $Path -Force | Select-Object -First 1)) {
|
|
Remove-Item -LiteralPath $Path -Force
|
|
} else {
|
|
throw "Refusing to replace existing path: $Path"
|
|
}
|
|
}
|
|
|
|
New-Item -ItemType Junction -Path $Path -Target $Target | Out-Null
|
|
}
|
|
|
|
function Upsert-ManagedBlock {
|
|
param(
|
|
[string]$Path,
|
|
[string]$BeginMarker,
|
|
[string]$EndMarker,
|
|
[string]$Body
|
|
)
|
|
|
|
Ensure-Directory (Split-Path -Parent $Path)
|
|
$Lines = if (Test-Path -LiteralPath $Path) { Get-Content -LiteralPath $Path } else { @() }
|
|
$Output = New-Object System.Collections.Generic.List[string]
|
|
$Skip = $false
|
|
|
|
foreach ($Line in $Lines) {
|
|
if ($Line -eq $BeginMarker) {
|
|
$Skip = $true
|
|
continue
|
|
}
|
|
|
|
if ($Line -eq $EndMarker) {
|
|
$Skip = $false
|
|
continue
|
|
}
|
|
|
|
if (-not $Skip) {
|
|
$Output.Add($Line)
|
|
}
|
|
}
|
|
|
|
if ($Output.Count -gt 0) {
|
|
$Output.Add('')
|
|
}
|
|
|
|
$Output.Add($BeginMarker)
|
|
foreach ($BodyLine in ($Body -split "`r?`n")) {
|
|
$Output.Add($BodyLine)
|
|
}
|
|
$Output.Add($EndMarker)
|
|
|
|
Set-Content -LiteralPath $Path -Value $Output
|
|
}
|
|
|
|
function Write-ManagedPowerShellEnv {
|
|
$script:ManagedShellEnv = Join-Path $StateDir 'copilot-cli-env.ps1'
|
|
Ensure-Directory (Split-Path -Parent $script:ManagedShellEnv)
|
|
|
|
@(
|
|
'$env:COPILOT_RESOURCES_HOME = ' + (Quote-SingleQuoted $CanonicalHome),
|
|
'$env:COPILOT_HOME = ' + (Quote-SingleQuoted $CopilotHome),
|
|
'$env:COPILOT_CUSTOM_INSTRUCTIONS_DIRS = ' + (Quote-SingleQuoted (Join-Path $CanonicalHome 'resources\instructions'))
|
|
) | Set-Content -LiteralPath $script:ManagedShellEnv
|
|
|
|
$QuotedEnvPath = Quote-SingleQuoted $script:ManagedShellEnv
|
|
Upsert-ManagedBlock \
|
|
-Path $ProfilePath \
|
|
-BeginMarker '# >>> copilot-resources bootstrap >>>' \
|
|
-EndMarker '# <<< copilot-resources bootstrap <<<' \
|
|
-Body "if (Test-Path -LiteralPath $QuotedEnvPath) {`n . $QuotedEnvPath`n}"
|
|
}
|
|
|
|
function Merge-VscodeSettings {
|
|
$NodeExecutable = Find-NodeExecutable
|
|
if (-not $NodeExecutable) {
|
|
Write-Warning 'Skipping VS Code settings merge because Node.js is not available.'
|
|
return
|
|
}
|
|
|
|
Ensure-Directory (Split-Path -Parent $VscodeSettingsFile)
|
|
& $NodeExecutable (Join-Path $ScriptDir 'merge-vscode-settings.mjs') --target $VscodeSettingsFile --template (Join-Path $CanonicalHome 'config\vscode\settings.template.jsonc') --set "COPILOT_RESOURCES_HOME=$CanonicalHome"
|
|
}
|
|
|
|
Ensure-Directory (Split-Path -Parent $CanonicalHome)
|
|
if (Test-Path -LiteralPath $CanonicalHome) {
|
|
if ((Resolve-Directory $CanonicalHome) -ne (Resolve-Directory $RepoRoot)) {
|
|
throw "Canonical path already exists and points elsewhere: $CanonicalHome"
|
|
}
|
|
} else {
|
|
New-Item -ItemType Junction -Path $CanonicalHome -Target $RepoRoot | Out-Null
|
|
}
|
|
|
|
Ensure-Directory $CopilotHome
|
|
Ensure-Directory $VscodeUserDir
|
|
|
|
Ensure-Junction -Target (Join-Path $CanonicalHome 'resources\skills') -Path (Join-Path $CopilotHome 'skills')
|
|
Ensure-Junction -Target (Join-Path $CanonicalHome 'resources\agents') -Path (Join-Path $CopilotHome 'agents')
|
|
Ensure-Junction -Target (Join-Path $CanonicalHome 'resources\instructions') -Path (Join-Path $CopilotHome 'instructions')
|
|
Ensure-Junction -Target (Join-Path $CanonicalHome 'resources\hooks') -Path (Join-Path $CopilotHome 'hooks')
|
|
Ensure-Junction -Target (Join-Path $CanonicalHome 'resources\prompts') -Path (Join-Path $VscodeUserDir 'prompts')
|
|
|
|
Write-ManagedPowerShellEnv
|
|
Merge-VscodeSettings
|
|
|
|
Ensure-Directory $StateDir
|
|
@{
|
|
canonicalHome = $CanonicalHome
|
|
repoRoot = $RepoRoot
|
|
copilotHome = $CopilotHome
|
|
vscodeUserDir = $VscodeUserDir
|
|
vscodeSettingsFile = $VscodeSettingsFile
|
|
shellRcFile = $ProfilePath
|
|
managedShellEnv = $ManagedShellEnv
|
|
bootstrapScript = (Join-Path $ScriptDir 'bootstrap.ps1')
|
|
} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $StateDir 'install-state.json')
|
|
|
|
Write-Host "Bootstrap complete."
|
|
Write-Host "Canonical home: $CanonicalHome"
|
|
Write-Host "Repository root: $RepoRoot"
|
|
Write-Host "Copilot home: $CopilotHome"
|
|
Write-Host "VS Code user dir: $VscodeUserDir"
|
|
Write-Host "Merged managed VS Code settings into: $VscodeSettingsFile"
|
|
Write-Host "Installed managed Copilot CLI PowerShell environment into: $ManagedShellEnv"
|
|
Write-Host "Linked PowerShell profile: $ProfilePath"
|
|
Write-Host "Optional VS Code template: $(Join-Path $CanonicalHome 'config\vscode\settings.template.jsonc')"
|