Extension System
Extensions are executable scripts that run at specific hook points during version management operations. They receive JSON input on stdin and return JSON output on stdout.
Start with Plugins
Most users only need built-in plugins. Extensions are for advanced customization when plugins don't meet your needs - such as custom team workflows, external tool integration, or prototyping new features before they become plugins.
Getting Started
There are three ways to work with extensions:
- Install existing extensions - Use pre-built extensions from the community (most common)
- Configure extensions - Enable/disable and customize installed extensions
- Create custom extensions - Build your own automation scripts (advanced)
Most users start by installing available extensions below.
Available Extensions
sley provides reference implementations you can install and use immediately:
| Extension | Language | Hook | Description |
|---|---|---|---|
| Commit Validator | Python | pre-bump | Validates conventional commit format |
| Docker Tag Sync | Bash | post-bump | Tags and pushes Docker images |
| GitHub Version Sync | Bash | pre-bump | Syncs version to latest GitHub release |
Ready to try one? See Installing Extensions below.
Browse the contrib/extensions/ directory for more examples in Python, Node.js, and Bash.
When to Use Extensions vs Plugins
Use Built-in Plugins When...
- Performance matters - Plugins execute in <1ms with native Go performance
- Feature is widely applicable - Common versioning needs across many projects
- Deep integration needed - Requires tight coupling with bump logic or validation
- Built-in reliability required - No external dependencies or installation steps
- Examples: Git tagging, conventional commit parsing, version validation, file syncing
Use Extensions When...
- Custom to your workflow - Organization-specific automation or processes
- Requires external tools - Need to call AWS CLI, curl, deployment scripts, etc.
- Prototyping new features - Testing ideas before proposing as built-in plugins
- Language-specific needs - Python/Node.js/Ruby tooling integration
- Examples: Custom notification systems, deployment triggers, proprietary tool integration
See the Plugin vs Extension Comparison Table for detailed feature differences.
Using Extensions
Installing Extensions
Extensions can be installed from remote repositories or local paths.
From Remote Repository
Install directly from any Git repository:
# Install from repository root
sley extension install --url github.com/user/my-extension
# Install from subdirectory in repository
sley extension install --url github.com/user/repo/path/to/extensionReal-world examples using sley's built-in extensions:
# Install commit-validator from sley repository subdirectory
sley extension install --url github.com/indaco/sley/contrib/extensions/commit-validator
# Install docker-tag-sync from sley repository subdirectory
sley extension install --url github.com/indaco/sley/contrib/extensions/docker-tag-syncSupported URL formats:
- Full URLs:
https://git-host.com/user/repoorhttps://git-host.com/user/repo/sub/dir - Shorthand:
git-host.com/user/repoorgit-host.com/user/repo/sub/dir
Works with any Git hosting service: GitHub, GitLab, Bitbucket, self-hosted GitLab/Gitea/Forgejo, GitHub Enterprise, and more.
Subdirectory Support
You can install extensions from any subdirectory in a repository. This is perfect for monorepos that contain multiple extensions or projects with extensions in a specific folder structure.
From Local Path
Install from a local directory:
# Install from local path
sley extension install --path /path/to/my-extension
# Install from relative path
sley extension install --path ./contrib/extensions/my-extensionPath vs URL
- Use
--pathfor local filesystem directories only - Use
--urlfor remote Git repositories (any Git hosting service) - These flags are mutually exclusive - you cannot use both
- Passing a URL to
--pathwill result in an error
Installation Process
Both methods copy the extension to ~/.sley-extensions/my-extension/ and add it to .sley.yaml:
# .sley.yaml
extensions:
- name: docker-tag-sync
path: /Users/username/.sley-extensions/docker-tag-sync
enabled: trueManaging Extensions
# List installed extensions
sley extension list
# Temporarily turn off an extension (keeps config and files)
sley extension disable --name docker-tag-sync
# Re-enable it later
sley extension enable --name docker-tag-sync
# Remove the extension entry from .sley.yaml
sley extension uninstall --name docker-tag-sync
# Also delete the extension directory from disk
sley extension uninstall --name docker-tag-sync --delete-folderdisable vs uninstall
disable toggles enabled: false in .sley.yaml - the extension stays registered and can be re-enabled at any time. uninstall removes the extension entry from the config entirely.
Running Extensions
Extensions run automatically during bump commands. Once installed, you'll see them execute:
$ sley bump patch
Running pre-bump extensions...
✓ commit-validator (0.05s)
Bumping version: 1.2.3 → 1.2.4
Running post-bump extensions...
✓ docker-tag-sync (0.12s)
Successfully bumped to 1.2.4Execution Flow
Extensions execute at specific points during bump operations:
sley bump patch
# 1. Pre-bump extensions run (can modify .version)
# 2. Pre-bump plugin validations run
# 3. Version bumped
# 4. Post-bump plugin actions run
# 5. Post-bump extensions runExtensions Run First
Pre-bump extensions execute before plugin validations. This allows extensions to set up state (e.g., fetch a version from an external source and update .version) before plugins like dependency-check validate consistency. After pre-bump extensions complete, sley re-reads the .version file to pick up any changes.
See the complete Execution Order diagram on the Plugins page.
Skipping Extensions
Skip extensions during a bump with --skip-hooks:
sley bump patch --skip-hooksChecking Installed Extensions
To see which extensions are active:
$ sley extension list
commit-validator (enabled) - Validates conventional commit format
docker-tag-sync (enabled) - Tags and pushes Docker images
custom-notifier (disabled) - Sends Slack notificationsConfiguration Reference
Extensions are configured in .sley.yaml:
# .sley.yaml
extensions:
# Remote extension
- name: commit-validator
path: /Users/username/.sley-extensions/commit-validator
enabled: true
# Local development extension
- name: custom-notifier
path: ./scripts/custom-notifier
enabled: false
# Extension with custom configuration
- name: github-version-sync
path: /Users/username/.sley-extensions/github-version-sync
enabled: true
config:
repo: indaco/sley
strip-prefix: v
# Another extension
- name: docker-tag-sync
path: /Users/username/.sley-extensions/docker-tag-sync
enabled: trueConfiguration options:
name: Extension identifier (required)path: Absolute or relative path to extension directory (required)enabled: Whether extension runs during bumps (default:true)config: Extension-specific key-value settings passed to the hook script (optional)
Troubleshooting
| Issue | Solution |
|---|---|
| Extension not found | Check sley extension list and verify path in .sley.yaml |
| Not executing | Verify enabled: true, script is executable, has proper shebang |
| Permission denied | Run chmod +x hook.sh on the extension's entry script |
| Timeout errors | Optimize script (30s timeout) or split into smaller tasks |
| Invalid JSON output | Test manually: echo '{"hook":"post-bump"}' | ./hook.sh |
| Extension fails | Check extension logs (stderr), verify dependencies installed |
Error Propagation
If an extension fails (success: false in JSON output), the bump operation is aborted and subsequent extensions are not executed. This prevents cascading failures.
Creating Extensions
For users who want to build custom extensions to automate organization-specific workflows.
Extension Concepts
Hook Points
Extensions run at specific lifecycle points:
| Hook | When | Use Cases |
|---|---|---|
pre-bump | Before version bump | Validate preconditions, run linters/tests, check dependencies |
post-bump | After successful bump | Update files, create tags, send notifications, deploy artifacts |
Future Hook Points
Additional hook points (pre-release, validate) are planned for future releases. Currently, sley supports pre-bump and post-bump hooks.
Hook Execution Order
When you run sley bump, hooks execute in this sequence:
- Pre-bump extensions run first (can modify
.version) - Plugin validations run (release-gate, version-validator, etc.)
- Version bumped (.version file updated)
- Plugin post-actions run (dependency-check sync, changelog, tags)
- Post-bump extensions run last (notifications, deployments)
See the complete execution flow diagram for details.
Input Context
All hooks receive JSON on stdin with context about the bump operation:
{
"hook": "post-bump",
"version": "1.2.3",
"previous_version": "1.2.2",
"bump_type": "patch",
"prerelease": "alpha",
"metadata": "build123",
"project_root": "/path/to/project",
"module_dir": "./services/api",
"module_name": "api",
"config": {
"repo": "indaco/sley",
"strip-prefix": "v"
}
}Monorepo Context
module_dir and module_name are included when running in a monorepo workspace. For single-project repositories, these fields are omitted.
Extension Configuration
The config field contains extension-specific settings from the .sley.yaml file. If no config is defined for the extension, this field is omitted from the JSON input.
Your First Extension
Directory Structure
Create a directory with these files:
my-extension/
extension.yaml # Manifest (required)
hook.sh # Entry point script
README.md # Documentation (recommended)Extension Manifest
Define your extension in extension.yaml:
# extension.yaml
schema_version: 1
name: my-extension
version: 1.0.0
description: Brief description of what this extension does
author: Your Name
repository: https://github.com/username/my-extension
entry: hook.sh
hooks:
- pre-bump
- post-bumpManifest fields:
schema_version: Schema version of the manifest format (optional, defaults to1)name: Extension identifier (required)version: Extension version (required)description: Human-readable description (required)author: Extension author (recommended)repository: Source code URL (recommended)entry: Script filename to execute (required)hooks: List of hook points this extension supports (required)
Manifest Versioning
The schema_version field enables backwards-compatible evolution of the manifest schema. If omitted, it defaults to 1. If your manifest uses a version newer than what your installed sley supports, you'll get a clear error prompting you to upgrade.
Hook Script Example
Create an executable script at the path specified in entry:
#!/bin/sh
# Read JSON input from stdin
read -r input
# Parse fields using grep/jq/python (choose based on your needs)
version=$(echo "$input" | grep -o '"version":"[^"]*"' | cut -d'"' -f4)
bump_type=$(echo "$input" | grep -o '"bump_type":"[^"]*"' | cut -d'"' -f4)
# Your custom logic here
echo "Processing version $version (bump: $bump_type)" >&2
# Perform actual work
# Example: Send notification, update external system, etc.
# Return success JSON on stdout
echo '{"success": true, "message": "Extension executed successfully"}'
exit 0Important:
- Make the script executable:
chmod +x hook.sh - Include proper shebang (
#!/bin/sh,#!/usr/bin/env python3, etc.) - Use stderr for logging, stdout only for JSON response
- Exit with code 0 for success, non-zero for failure
See contrib/extensions/ in the sley repository for complete examples in Python, Node.js, and Bash.
Implementation Details
JSON Output Format
Extensions must return JSON on stdout:
Success response:
{
"success": true,
"message": "Optional status message",
"data": {
"key": "Optional data to return"
}
}Failure response:
{
"success": false,
"message": "Extension failed: reason for failure"
}When success: false, the bump operation aborts immediately.
Multi-Language Examples
Python:
#!/usr/bin/env python3
import sys
import json
# Read input
input_data = json.loads(sys.stdin.read())
version = input_data.get("version")
hook = input_data.get("hook")
# Your logic here
print(f"Processing {hook} for version {version}", file=sys.stderr)
# Return result
result = {
"success": True,
"message": f"Extension processed {version}"
}
print(json.dumps(result))
sys.exit(0)Node.js:
#!/usr/bin/env node
const input = JSON.parse(require("fs").readFileSync(0, "utf-8"));
console.error(`Processing ${input.hook} for ${input.version}`);
// Your logic here
const result = {
success: true,
message: `Extension processed ${input.version}`,
};
console.log(JSON.stringify(result));
process.exit(0);Browse contrib/extensions/ for production-ready examples.
Error Handling
Handle errors gracefully and return meaningful messages:
#!/bin/sh
read -r input
# Validate input
if [ -z "$input" ]; then
echo '{"success": false, "message": "No input received"}'
exit 1
fi
# Parse version
version=$(echo "$input" | grep -o '"version":"[^"]*"' | cut -d'"' -f4)
if [ -z "$version" ]; then
echo '{"success": false, "message": "Version field missing from input"}'
exit 1
fi
# Perform work with error checking
if ! some_command "$version"; then
echo '{"success": false, "message": "Command failed: some_command"}' >&2
exit 1
fi
echo '{"success": true, "message": "Extension completed"}'
exit 0Best Practices
- Keep extensions focused - One extension should do one thing well
- Validate input - Check for required fields before processing
- Handle errors gracefully - Return meaningful error messages in JSON
- Test with different inputs - Verify behavior across hook types and version formats
- Use stderr for logging - Reserve stdout exclusively for JSON output
- Make scripts executable - Use
chmod +xand include proper shebang - Document dependencies - List required tools in README (e.g., jq, curl, python3)
- Set exit codes - Exit 0 for success, non-zero for failure
- Respect timeouts - Extensions have a 30-second execution limit
- Test manually - Use
echo '{"hook":"post-bump","version":"1.0.0"}' | ./hook.shto verify output
Security Considerations
Be mindful of security when creating and installing extensions:
- Extensions run with your user permissions - Only install from trusted sources
- 30-second execution timeout - Prevents hanging processes
- 1MB output limit - Prevents memory exhaustion
- Process isolation - Each extension runs as a separate process
- No network restrictions - Extensions can make network calls (be cautious)
- File system access - Extensions can read/write files with your permissions
When installing extensions:
- Review extension code before installation
- Prefer well-maintained extensions with clear documentation
- Check the repository's commit history and community
- Use local paths during development, URLs for trusted sources
See Also
- Plugin System - Built-in plugins vs extensions comparison
- Commit Validator Extension - Validate conventional commits
- Docker Tag Sync Extension - Tag Docker images with versions
- GitHub Version Sync Extension - Sync version to latest GitHub release
- CI/CD Integration - Use extensions in automation pipelines
- .sley.yaml Reference - Extension configuration reference
- Troubleshooting - Common extension issues