Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,56 @@ for cmd in curl git; do
done

if [[ ${#MISSING_DEPS[@]} -gt 0 ]]; then
log_error "Missing required dependencies: ${MISSING_DEPS[*]}"
log_hint "Please install them first:"
# {{ if and (eq .chezmoi.os "linux") (eq .chezmoi.osRelease.id "ubuntu" "debian") }}
log_hint " sudo apt-get install -y ${MISSING_DEPS[*]}"
# {{ else if and (eq .chezmoi.os "linux") (eq .chezmoi.osRelease.id "fedora" "rhel" "centos") }}
log_hint " sudo dnf install -y ${MISSING_DEPS[*]}"
log_warn "Missing required dependencies: ${MISSING_DEPS[*]}"

# {{ if eq .chezmoi.os "linux" }}
# On Linux, Homebrew is optional and its prerequisites (git, curl) are
# also installed later by the package-install script. On a clean system
# (e.g. a fresh WSL distro) they may not be present yet, so try to
# install them automatically. If that isn't possible, skip Homebrew
# rather than aborting the whole chezmoi apply.
# {{ if eq .chezmoi.osRelease.id "ubuntu" "debian" }}
if sudo -n true 2>/dev/null; then
log_step "Installing missing dependencies with apt-get: ${MISSING_DEPS[*]}"
DEBIAN_FRONTEND=noninteractive sudo apt-get update -qq || true
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y "${MISSING_DEPS[@]}" || true
else
log_warn "Cannot install dependencies automatically (no passwordless sudo)"
fi
# {{ else if eq .chezmoi.osRelease.id "fedora" "rhel" "centos" }}
if sudo -n true 2>/dev/null; then
log_step "Installing missing dependencies with dnf: ${MISSING_DEPS[*]}"
sudo dnf install -y "${MISSING_DEPS[@]}" || true
else
log_warn "Cannot install dependencies automatically (no passwordless sudo)"
fi
# {{ end }}

# Re-check after the install attempt.
STILL_MISSING=()
for cmd in "${MISSING_DEPS[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
STILL_MISSING+=("$cmd")
fi
done

if [[ ${#STILL_MISSING[@]} -gt 0 ]]; then
log_warn "Still missing dependencies: ${STILL_MISSING[*]}"
log_info "Homebrew is optional on Linux - skipping installation."
log_hint "Install them manually if you need Homebrew later:"
# {{ if eq .chezmoi.osRelease.id "ubuntu" "debian" }}
log_hint " sudo apt-get install -y ${STILL_MISSING[*]}"
# {{ else if eq .chezmoi.osRelease.id "fedora" "rhel" "centos" }}
log_hint " sudo dnf install -y ${STILL_MISSING[*]}"
# {{ end }}
exit 0
fi
log_result "Dependencies installed successfully"
# {{ else }}
log_error "Missing required dependencies: ${MISSING_DEPS[*]}"
log_hint "Please install them first, then re-run chezmoi apply."
exit 1
# {{ end }}
fi

# Check for sudo permissions (only relevant on Linux)
Expand Down
17 changes: 17 additions & 0 deletions tests/bash/test-chezmoi-scripts.bats
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ strip_template() {
[ "$status" -eq 0 ]
}

# On Linux, Homebrew is optional, so missing prerequisites (e.g. git on a
# clean WSL distro) must not abort the whole chezmoi apply. The Linux branch
# should try to install the dependencies and otherwise skip gracefully.
@test "chezmoi-scripts: install-homebrew attempts to install missing dependencies on Linux" {
local script="$LINUX_SCRIPTS_DIR/run_once_before_05-install-homebrew.sh.tmpl"
grep -q "apt-get install -y \"\${MISSING_DEPS\[@\]}\"" "$script"
grep -q "dnf install -y \"\${MISSING_DEPS\[@\]}\"" "$script"
}

@test "chezmoi-scripts: install-homebrew does not hard-fail on Linux when deps are missing" {
local script="$LINUX_SCRIPTS_DIR/run_once_before_05-install-homebrew.sh.tmpl"
# The Linux dependency path skips gracefully (exit 0); only the macOS
# branch (after the template else) is allowed to exit 1.
grep -q "Homebrew is optional on Linux - skipping installation." "$script"
grep -q "STILL_MISSING" "$script"
}

@test "chezmoi-scripts: run_once_install-lefthook.sh.tmpl exists" {
[ -f "$LINUX_SCRIPTS_DIR/run_once_install-lefthook.sh.tmpl" ]
}
Expand Down
4 changes: 3 additions & 1 deletion tests/bash/test-package-utils.bats
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ setup() {
}

@test "package-utils: Windows package IDs can match by package suffix" {
run bash -c "source \"$PACKAGE_UTILS\" && package_required_for_install_type mise full windows \"$PACKAGES_FILE\""
# Windows winget uses reverse-DNS IDs (e.g. "twpayne.chezmoi"), which
# must match a plain package name ("chezmoi") by suffix.
run bash -c "source \"$PACKAGE_UTILS\" && package_required_for_install_type chezmoi full windows \"$PACKAGES_FILE\""
[ "$status" -eq 0 ]
}

Expand Down
49 changes: 30 additions & 19 deletions tests/powershell/CopilotSsh.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ echo FWD_GH=%GH_TOKEN%
function script:Remove-OpStub {
Remove-Item (Join-Path $script:StubDir 'op.cmd') -Force -ErrorAction SilentlyContinue
}

# Invoke Connect-CopilotSsh in error-path scenarios while capturing stdout and
# the resulting error records separately so tests can assert on both streams.
function script:Invoke-CopilotSshError {
Remove-Variable capturedErr -Scope Local -ErrorAction SilentlyContinue
$out = (Connect-CopilotSsh myhost -ErrorAction SilentlyContinue -ErrorVariable capturedErr) | Out-String
[PSCustomObject]@{
Output = $out
ErrorText = $capturedErr | Out-String
}
}
}

AfterAll {
Expand Down Expand Up @@ -175,28 +186,28 @@ Describe "Connect-CopilotSsh behaviour" -Tag "Unit" -Skip:(-not $script:IsWindow
# pre-flight check must fire first and abort.
$emptyDir = (New-Item -ItemType Directory -Path (Join-Path $script:StubDir "nopath-$(Get-Random)")).FullName
$env:PATH = $emptyDir
$out = (Connect-CopilotSsh myhost *>&1) | Out-String
$out | Should -Match "'ssh' \(OpenSSH client\) was not found"
$out | Should -Not -Match 'SSH_ARGS:'
$result = Invoke-CopilotSshError
$result.ErrorText | Should -Match "'ssh' \(OpenSSH client\) was not found"
$result.Output | Should -Not -Match 'SSH_ARGS:'
}

It "Aborts without invoking ssh when op is not installed" {
script:Remove-OpStub
$out = (Connect-CopilotSsh myhost *>&1) | Out-String
$out | Should -Match "'1Password CLI' \(op\) was not found"
$out | Should -Match 'Install it'
$out | Should -Match 'Settings -> Developer'
$out | Should -Not -Match 'SSH_ARGS:'
$out | Should -Not -Match 'SendEnv'
$result = Invoke-CopilotSshError
$result.ErrorText | Should -Match "'1Password CLI' \(op\) was not found"
$result.ErrorText | Should -Match 'Install it'
$result.ErrorText | Should -Match 'Settings -> Developer'
$result.Output | Should -Not -Match 'SSH_ARGS:'
$result.Output | Should -Not -Match 'SendEnv'
}

It "Aborts without invoking ssh when the Environment ID is unset" {
script:New-OpStub -Stdout "ctok$($script:Tab)gtok"
Remove-Item Env:OP_COPILOT_ENVIRONMENT_ID -ErrorAction SilentlyContinue
$out = (Connect-CopilotSsh myhost *>&1) | Out-String
$out | Should -Match 'OP_COPILOT_ENVIRONMENT_ID is not set'
$out | Should -Not -Match 'SSH_ARGS:'
$out | Should -Not -Match 'SendEnv'
$result = Invoke-CopilotSshError
$result.ErrorText | Should -Match 'OP_COPILOT_ENVIRONMENT_ID is not set'
$result.Output | Should -Not -Match 'SSH_ARGS:'
$result.Output | Should -Not -Match 'SendEnv'
}

It "Forwards both tokens when present" {
Expand Down Expand Up @@ -224,16 +235,16 @@ Describe "Connect-CopilotSsh behaviour" -Tag "Unit" -Skip:(-not $script:IsWindow

It "Errors when COPILOT_GITHUB_TOKEN is missing from the Environment" {
script:New-OpStub -Stdout ''
$out = (Connect-CopilotSsh myhost *>&1) | Out-String
$out | Should -Match 'COPILOT_GITHUB_TOKEN not found'
$out | Should -Not -Match 'SSH_ARGS:'
$result = Invoke-CopilotSshError
$result.ErrorText | Should -Match 'COPILOT_GITHUB_TOKEN not found'
$result.Output | Should -Not -Match 'SSH_ARGS:'
}

It "Errors with a dedicated message when op run fails" {
script:New-OpStub -ExitCode 3
$out = (Connect-CopilotSsh myhost *>&1) | Out-String
$out | Should -Match 'failed to read tokens'
$out | Should -Not -Match 'SSH_ARGS:'
$result = Invoke-CopilotSshError
$result.ErrorText | Should -Match 'failed to read tokens'
$result.Output | Should -Not -Match 'SSH_ARGS:'
}
}

Expand Down
28 changes: 18 additions & 10 deletions tests/powershell/Packages.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,19 @@ Describe "Windows Package Configuration" {
$lightPackages | Should -Contain "twpayne.chezmoi"
}

It "Full mode should include development tools (VSCode, Terminal)" {
It "Full mode should include development tools (VSCode, WSL, OhMyPosh)" {
$fullPackages = $script:ChezmoiData.packages.windows.winget.full
$fullPackages | Should -Contain "Microsoft.VisualStudioCode"
$fullPackages | Should -Contain "Microsoft.WindowsTerminal"
$fullPackages | Should -Contain "Microsoft.WSL"
$fullPackages | Should -Contain "JanDeDobbeleer.OhMyPosh"
}

It "Windows Terminal should be configured separately from the winget full list" {
$fullPackages = $script:ChezmoiData.packages.windows.winget.full
$fullPackages | Should -Not -Contain "Microsoft.WindowsTerminal"

$terminalSetupScript = Join-Path $script:RepoRoot "home\.chezmoiscripts\windows\run_once_setup-windows-terminal.ps1"
Test-Path $terminalSetupScript | Should -Be $true
}

It "Full mode should include WSL" {
Expand Down Expand Up @@ -259,17 +268,16 @@ Describe "Windows Package Configuration" {
$script:ChezmoiData.packages.windows.powershell_git_modules | Get-Member -Name "light" | Should -Not -BeNullOrEmpty
}

It "Should have PowerShell Git full mode modules" {
$script:ChezmoiData.packages.windows.powershell_git_modules.full | Should -Not -BeNullOrEmpty
It "Should have PowerShell Git full mode modules property" {
$script:ChezmoiData.packages.windows.powershell_git_modules.PSObject.Properties.Name | Should -Contain 'full'
}

It "Full mode should include Git-based modules" {
It "Full mode should leave Git-based modules empty" {
$fullGitModules = $script:ChezmoiData.packages.windows.powershell_git_modules.full
# At least one module should be defined
$fullGitModules.Count | Should -BeGreaterThan 0
$fullGitModules | Should -BeNullOrEmpty
}

It "Git modules should have required properties (name, url, destination)" {
It "Configured Git modules should have required properties (name, url, destination)" {
$allGitModules = @()
if ($script:ChezmoiData.packages.windows.powershell_git_modules.light) {
$allGitModules += $script:ChezmoiData.packages.windows.powershell_git_modules.light
Expand All @@ -285,7 +293,7 @@ Describe "Windows Package Configuration" {
}
}

It "Git module URLs should be valid GitHub URLs" {
It "Configured Git module URLs should be valid GitHub URLs" {
$allGitModules = @()
if ($script:ChezmoiData.packages.windows.powershell_git_modules.light) {
$allGitModules += $script:ChezmoiData.packages.windows.powershell_git_modules.light
Expand All @@ -299,7 +307,7 @@ Describe "Windows Package Configuration" {
}
}

It "Git module destinations should not contain invalid path characters" {
It "Configured Git module destinations should not contain invalid path characters" {
$allGitModules = @()
if ($script:ChezmoiData.packages.windows.powershell_git_modules.light) {
$allGitModules += $script:ChezmoiData.packages.windows.powershell_git_modules.light
Expand Down