name: Release
on:
push:
tags:
- 'v*.*.*'
env:
CARGO_TERM_COLOR: always
jobs:
validate:
name: Validate Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "📦 Release version: $VERSION"
- name: Verify tag matches Cargo.toml version
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/v}
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
echo "❌ Tag version ($TAG_VERSION) doesn't match Cargo.toml ($CARGO_VERSION)"
exit 1
fi
echo "✅ Tag matches Cargo.toml version: $TAG_VERSION"
- name: Verify SDK versions match
run: |
TAG_VERSION=${GITHUB_REF#refs/tags/v}
PYTHON_VERSION=$(grep '^version' sdk/python/pyproject.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
JS_VERSION=$(grep '"version"' sdk/javascript/package.json | head -1 | sed 's/.*"\([0-9.]*\)".*/\1/')
if [ "$TAG_VERSION" != "$PYTHON_VERSION" ]; then
echo "❌ Python SDK version ($PYTHON_VERSION) doesn't match tag ($TAG_VERSION)"
exit 1
fi
if [ "$TAG_VERSION" != "$JS_VERSION" ]; then
echo "❌ JavaScript SDK version ($JS_VERSION) doesn't match tag ($TAG_VERSION)"
exit 1
fi
echo "✅ All SDK versions match: $TAG_VERSION"
- name: Verify CHANGELOG has entry
run: |
VERSION=${GITHUB_REF#refs/tags/v}
if ! grep -q "\[$VERSION\]" CHANGELOG.md; then
echo "❌ Version $VERSION not found in CHANGELOG.md"
exit 1
fi
echo "✅ CHANGELOG entry found for $VERSION"
build:
name: Build & Test
runs-on: ubuntu-latest
needs: validate
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
- name: Check formatting
run: cargo fmt --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build release
run: cargo build --release
- name: Run unit tests
run: cargo test --all-features
- name: Test CLI headless commands
run: |
# Version check
./target/release/lazy-locker --version
# Init with force
./target/release/lazy-locker init --passphrase "testpass" --force
# Token operations
./target/release/lazy-locker token add TEST_KEY "test_value" --passphrase "testpass"
./target/release/lazy-locker token add EXPIRING_KEY "expires_soon" --expires 30 --passphrase "testpass"
# List tokens
./target/release/lazy-locker token list --passphrase "testpass"
./target/release/lazy-locker token list --json --passphrase "testpass"
# Get token
VALUE=$(./target/release/lazy-locker token get TEST_KEY --passphrase "testpass")
if [ "$VALUE" != "test_value" ]; then
echo "❌ Token get failed: expected 'test_value', got '$VALUE'"
exit 1
fi
# Import from stdin
echo -e "IMPORT_KEY1=value1\nIMPORT_KEY2=value2" | ./target/release/lazy-locker import --stdin --passphrase "testpass"
# Export
./target/release/lazy-locker export --env --passphrase "testpass"
# Remove token
./target/release/lazy-locker token remove TEST_KEY --passphrase "testpass"
echo "✅ All CLI tests passed"
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: lazy-locker-linux-x86_64
path: target/release/lazy-locker
retention-days: 7
publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: build
environment:
name: cargo
url: https://crates.io/crates/lazy-locker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-publish-${{ hashFiles('**/Cargo.lock') }}
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}
run: |
cargo publish --no-verify
echo "✅ Published to crates.io"
publish-pypi:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: build
permissions:
id-token: write contents: read
environment:
name: pypi
url: https://pypi.org/p/lazy-locker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build
- name: Build Python package
working-directory: sdk/python
run: python -m build
- name: Publish to PyPI (Trusted Publishing)
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: sdk/python/dist/
password: ${{ secrets.PYPI_API_TOKEN }}
skip-existing: true
publish-npm:
name: Publish to npm
runs-on: ubuntu-latest
needs: build
permissions:
id-token: write contents: read
environment:
name: npm
url: https://www.npmjs.com/package/lazy-locker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Update npm to latest (required for trusted publishing)
run: npm install -g npm@latest
- name: Verify npm version
run: |
NPM_VERSION=$(npm --version)
echo "npm version: $NPM_VERSION"
# npm 11.5.1+ required for trusted publishing
MAJOR=$(echo $NPM_VERSION | cut -d. -f1)
if [ "$MAJOR" -lt 11 ]; then
echo "⚠️ npm version < 11, trusted publishing may not work"
fi
- name: Install dependencies
working-directory: sdk/javascript
run: npm ci || npm install
- name: Build TypeScript
working-directory: sdk/javascript
run: npm run build
- name: Publish to npm with provenance
working-directory: sdk/javascript
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_API_TOKEN }}
run: |
npm publish --access public --provenance
echo "✅ Published to npm with provenance"
github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [validate, publish-crates, publish-pypi, publish-npm]
permissions:
contents: write steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download binary artifact
uses: actions/download-artifact@v4
with:
name: lazy-locker-linux-x86_64
path: ./artifacts
- name: Make binary executable
run: chmod +x ./artifacts/lazy-locker
- name: Rename binary with platform suffix
run: mv ./artifacts/lazy-locker ./artifacts/lazy-locker-linux-x86_64
- name: Extract changelog for this version
id: changelog
run: |
VERSION=${{ needs.validate.outputs.version }}
# Extract changelog section for this version
CHANGELOG=$(awk "/## \[$VERSION\]/{flag=1; next} /## \[/{flag=0} flag" CHANGELOG.md)
# Use delimiter for multiline output
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: v${{ needs.validate.outputs.version }}
tag_name: v${{ needs.validate.outputs.version }}
body: |
## What's Changed
${{ steps.changelog.outputs.changelog }}
## Installation
### 🦀 Rust (crates.io)
```bash
cargo install lazy-locker
```
### 🐍 Python (PyPI)
```bash
pip install lazy-locker
```
### 📦 Node.js (npm)
```bash
npm install -g lazy-locker
```
### 💾 Binary
Download the pre-built binary for Linux x86_64 from the assets below.
```bash
chmod +x lazy-locker-linux-x86_64
sudo mv lazy-locker-linux-x86_64 /usr/local/bin/lazy-locker
```
## Verify Installation
```bash
lazy-locker --version
```
files: |
./artifacts/lazy-locker-linux-x86_64
draft: false
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}