|
| 1 | +# E2E for `fc3 skill install/update` on Windows. |
| 2 | +# Fully sandboxed and offline; needs NO cloud credentials. |
| 3 | +# |
| 4 | +# The local component build is loaded through a minimal s.yaml that points at |
| 5 | +# the repo root (component: <fc3_dir>) and is driven with `s skill ...`. We do |
| 6 | +# NOT use `s cli <abs-path> ...`: on Windows the CLI joins that absolute path |
| 7 | +# into its per-run log directory, which breaks when cwd (the temp sandbox on |
| 8 | +# C:) and the repo (on D:) live on different drives. |
| 9 | + |
| 10 | +$ErrorActionPreference = "Stop" |
| 11 | + |
| 12 | +$current_dir = $PWD.Path |
| 13 | +$fc3_dir = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $current_dir)) |
| 14 | +Write-Host "fc3 root dir: $fc3_dir" |
| 15 | + |
| 16 | +$tools = @("claude", "codex", "cursor", "qoder", "agents") |
| 17 | + |
| 18 | +$projectRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-proj-" + [System.Guid]::NewGuid().ToString("N")) |
| 19 | +$homeRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-home-" + [System.Guid]::NewGuid().ToString("N")) |
| 20 | +$workRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-work-" + [System.Guid]::NewGuid().ToString("N")) |
| 21 | +$filterRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("fc3-skill-filter-" + [System.Guid]::NewGuid().ToString("N")) |
| 22 | +New-Item -ItemType Directory -Force -Path $projectRoot | Out-Null |
| 23 | +New-Item -ItemType Directory -Force -Path $homeRoot | Out-Null |
| 24 | +New-Item -ItemType Directory -Force -Path $workRoot | Out-Null |
| 25 | +New-Item -ItemType Directory -Force -Path $filterRoot | Out-Null |
| 26 | + |
| 27 | +# Drop a minimal offline s.yaml that loads the local build. The component path |
| 28 | +# is single-quoted so backslashes stay literal in YAML. |
| 29 | +function Write-SYaml($dir) { |
| 30 | + $yaml = @" |
| 31 | +edition: 3.0.0 |
| 32 | +name: skill-e2e |
| 33 | +resources: |
| 34 | + fc3: |
| 35 | + component: '$fc3_dir' |
| 36 | + props: {} |
| 37 | +"@ |
| 38 | + Set-Content -Path (Join-Path $dir "s.yaml") -Value $yaml -Encoding utf8 |
| 39 | +} |
| 40 | + |
| 41 | +function Assert-File($p) { |
| 42 | + if (-not (Test-Path -PathType Leaf $p)) { |
| 43 | + Write-Host "ASSERT FAILED: expected file not found: $p" -ForegroundColor Red |
| 44 | + exit 1 |
| 45 | + } |
| 46 | + Write-Host "OK: $p" |
| 47 | +} |
| 48 | + |
| 49 | +function Assert-Missing($p) { |
| 50 | + if (Test-Path $p) { |
| 51 | + Write-Host "ASSERT FAILED: expected path to be gone: $p" -ForegroundColor Red |
| 52 | + exit 1 |
| 53 | + } |
| 54 | + Write-Host "OK (absent): $p" |
| 55 | +} |
| 56 | + |
| 57 | +function Skill-Path($root, $tool) { |
| 58 | + return (Join-Path $root ".$tool\skills\s-fc3\SKILL.md") |
| 59 | +} |
| 60 | + |
| 61 | +Write-SYaml $projectRoot |
| 62 | +Write-SYaml $workRoot |
| 63 | +Write-SYaml $filterRoot |
| 64 | + |
| 65 | +try { |
| 66 | + Write-Host "=== project-scope install (all tools) ===" |
| 67 | + Set-Location $projectRoot |
| 68 | + s skill install --project |
| 69 | + foreach ($t in $tools) { Assert-File (Skill-Path $projectRoot $t) } |
| 70 | + |
| 71 | + Write-Host "=== install is idempotent: existing target is skipped ===" |
| 72 | + $marker = Join-Path $projectRoot ".claude\skills\s-fc3\LOCAL_MARKER" |
| 73 | + Set-Content -Path $marker -Value "keep-me" |
| 74 | + s skill install --project --tools claude |
| 75 | + Assert-File $marker |
| 76 | + |
| 77 | + Write-Host "=== --force overwrites and cleans stale files ===" |
| 78 | + s skill install --project --tools claude --force |
| 79 | + Assert-Missing $marker |
| 80 | + Assert-File (Skill-Path $projectRoot "claude") |
| 81 | + |
| 82 | + Write-Host "=== update overwrites existing installations ===" |
| 83 | + Set-Content -Path $marker -Value "stale" |
| 84 | + s skill update --project --tools claude |
| 85 | + Assert-Missing $marker |
| 86 | + Assert-File (Skill-Path $projectRoot "claude") |
| 87 | + |
| 88 | + Write-Host "=== --tools filter installs only the requested tools ===" |
| 89 | + Set-Location $filterRoot |
| 90 | + s skill install --project --tools codex |
| 91 | + Assert-File (Skill-Path $filterRoot "codex") |
| 92 | + Assert-Missing (Join-Path $filterRoot ".cursor\skills\s-fc3") |
| 93 | + |
| 94 | + Write-Host "=== global-scope install writes under the sandboxed home directory ===" |
| 95 | + Set-Location $workRoot |
| 96 | + $oldHome = $env:USERPROFILE |
| 97 | + try { |
| 98 | + $env:USERPROFILE = $homeRoot |
| 99 | + s skill install --global --tools "claude,codex" |
| 100 | + } finally { |
| 101 | + $env:USERPROFILE = $oldHome |
| 102 | + } |
| 103 | + Assert-File (Skill-Path $homeRoot "claude") |
| 104 | + Assert-File (Skill-Path $homeRoot "codex") |
| 105 | + |
| 106 | + Write-Host "=== skill e2e passed ===" |
| 107 | +} finally { |
| 108 | + # Restore cwd to where this script started (the `skill` dir) so the caller's |
| 109 | + # `cd ..` returns to the e2e root. Restoring to $fc3_dir instead would leave |
| 110 | + # the caller one level too high. $current_dir is outside the temp sandboxes, |
| 111 | + # so it is safe to sit here while they are removed. |
| 112 | + Set-Location $current_dir |
| 113 | + Remove-Item -Recurse -Force $projectRoot -ErrorAction SilentlyContinue |
| 114 | + Remove-Item -Recurse -Force $homeRoot -ErrorAction SilentlyContinue |
| 115 | + Remove-Item -Recurse -Force $workRoot -ErrorAction SilentlyContinue |
| 116 | + Remove-Item -Recurse -Force $filterRoot -ErrorAction SilentlyContinue |
| 117 | +} |
0 commit comments