Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .chezmoidata/packages.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
# Package declarations for declarative installation
# Packages are installed via run_onchange scripts that execute when this file changes

npm:
global:
- "@anthropic-ai/claude-code"
41 changes: 41 additions & 0 deletions .chezmoiscripts/run_onchange_after_install-npm-packages.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
# Install npm packages declaratively based on .chezmoidata/packages.yaml
# This script runs when the package list changes

{{ if .include_defaults -}}
set -e

# Function to run npm commands via mise
run_npm() {
if command -v mise >/dev/null 2>&1; then
mise exec -- npm "$@"
else
npm "$@"
fi
}

# Check if npm is available (via mise or directly)
if command -v mise >/dev/null 2>&1; then
if ! mise exec -- npm --version >/dev/null 2>&1; then
echo "⚠️ Node.js/npm not available via mise. Skipping npm package installation."
exit 0
fi
elif ! command -v npm >/dev/null 2>&1; then
echo "⚠️ npm not found. Skipping npm package installation."
exit 0
fi

# Install global npm packages
{{ if .npm.global -}}
{{ range .npm.global -}}
if ! run_npm list -g "{{ . }}" >/dev/null 2>&1; then
echo "📦 Installing {{ . }}..."
run_npm install -g "{{ . }}"
echo "✓ Installed {{ . }}"
else
echo "✓ {{ . }} already installed"
fi
{{ end -}}
{{ end -}}

{{ end -}}
74 changes: 74 additions & 0 deletions .claude/skills/chezmoi-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,80 @@ if ! command -v jq &> /dev/null; then
fi
```

## Declarative Package Installation

Chezmoi can install packages declaratively using a combination of `.chezmoidata/packages.yaml` and `run_onchange_` scripts. This pattern ensures packages are installed when the package list changes.

### Pattern: npm Package Installation

**1. Declare packages in `.chezmoidata/packages.yaml`:**
```yaml
---
# Package declarations for declarative installation
# Top-level keys become template variables (e.g., .npm, .apt, .brew)
npm:
global:
- "@anthropic-ai/claude-code"
- "typescript"
```

**2. Create installation script `.chezmoiscripts/run_onchange_after_install-npm-packages.sh.tmpl`:**
```bash
#!/bin/bash
# Install npm packages declaratively based on .chezmoidata/packages.yaml
# This script runs when the package list changes

{{ if .include_defaults -}}
set -e

# Function to run npm commands via mise
run_npm() {
if command -v mise >/dev/null 2>&1; then
mise exec -- npm "$@"
else
npm "$@"
fi
}

# Check if npm is available (via mise or directly)
if command -v mise >/dev/null 2>&1; then
if ! mise exec -- npm --version >/dev/null 2>&1; then
echo "⚠️ Node.js/npm not available via mise. Skipping npm package installation."
exit 0
fi
elif ! command -v npm >/dev/null 2>&1; then
echo "⚠️ npm not found. Skipping npm package installation."
exit 0
fi

# Install global npm packages
{{ if .npm.global -}}
{{ range .npm.global -}}
if ! run_npm list -g "{{ . }}" >/dev/null 2>&1; then
echo "📦 Installing {{ . }}..."
run_npm install -g "{{ . }}"
echo "✓ Installed {{ . }}"
else
echo "✓ {{ . }} already installed"
fi
{{ end -}}
{{ end -}}

{{ end -}}
```

**How it works:**
- The script template references `.npm.global` from `.chezmoidata/packages.yaml`
- `run_onchange_` prefix means the script executes when its rendered content changes
- When you add/remove packages in `packages.yaml`, the rendered script changes, triggering re-execution
- Each package is checked before installation to avoid redundant installs
- Uses `mise exec` to ensure npm is available from mise-managed Node.js

**Adaptable to other package managers:**
- apt: Create `.chezmoidata/packages.yaml` with `apt: [...]` and use `{{ range .apt }}`
- brew: Create with `brew: [...]` and use `{{ range .brew }}`
- pip: Create with `pip: [...]` and use `{{ range .pip }}`

## Reference Documentation

For a complete, working example of this pattern, see:
Expand Down
2 changes: 1 addition & 1 deletion test/test-coder-existing.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e

# Create existing ~/.claude with user content
mkdir -p "$HOME/.claude"
echo "user_settings" > "$HOME/.claude/settings.json"
echo '{"alwaysThinkingEnabled":true,"userSetting":"value"}' > "$HOME/.claude/settings.json"
echo "user_custom" > "$HOME/.claude/custom.txt"

# Run setup
Expand Down
54 changes: 53 additions & 1 deletion test/verify-marketplace.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#!/bin/bash
# Verify marketplace configuration
# Verify marketplace configuration and npm packages
set -e

# Ensure mise is activated if available
if command -v mise >/dev/null 2>&1; then
export PATH="$HOME/.local/bin:$PATH"
eval "$(mise activate bash 2>/dev/null)" || true
fi

if [ ! -f "$HOME/.claude/settings.json" ]; then
echo "✗ settings.json not created"
exit 1
Expand All @@ -18,3 +24,49 @@ if ! grep -q "git@github.com:fx/cc.git" "$HOME/.claude/settings.json"; then
fi

echo "✓ Claude marketplace configured correctly"

# Verify npm package installation
# Helper function to run npm via mise
run_npm() {
if command -v mise >/dev/null 2>&1; then
mise exec -- npm "$@"
else
npm "$@"
fi
}

# Check if npm is available
if command -v mise >/dev/null 2>&1; then
if ! mise exec -- npm --version >/dev/null 2>&1; then
echo "⚠️ npm not available, skipping package verification"
exit 0
fi
elif ! command -v npm >/dev/null 2>&1; then
echo "⚠️ npm not available, skipping package verification"
exit 0
fi

# Parse packages.yaml and verify each npm global package
if command -v yq >/dev/null 2>&1 && [ -f "$HOME/.local/share/chezmoi/.chezmoidata/packages.yaml" ]; then
# Use yq to parse the YAML file
npm_packages=$(yq eval '.npm.global[]' "$HOME/.local/share/chezmoi/.chezmoidata/packages.yaml" 2>/dev/null || echo "")
if [ -n "$npm_packages" ]; then
while IFS= read -r package; do
if [ -n "$package" ]; then
# npm list can exit non-zero due to warnings, so check the output instead
if ! run_npm list -g "$package" 2>&1 | grep -qF "$package@"; then
echo "✗ npm package $package not installed"
exit 1
fi
echo "✓ npm package $package installed correctly"
fi
done <<< "$npm_packages"
fi
else
# Fallback: Check known packages if yq is not available
if ! run_npm list -g @anthropic-ai/claude-code 2>&1 | grep -qF "@anthropic-ai/claude-code@"; then
echo "✗ @anthropic-ai/claude-code not installed"
exit 1
fi
echo "✓ @anthropic-ai/claude-code installed correctly"
fi