chore: add CI for github and woodpecker
Some checks failed
CI / Lint (bash) (push) Has been cancelled
CI / Lint (markdown) (push) Has been cancelled
CI / Lint (nickel) (push) Has been cancelled
CI / Lint (nushell) (push) Has been cancelled
CI / Lint (rust) (push) Has been cancelled
CI / Code Coverage (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
CI / Benchmark (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / License Compliance (push) Has been cancelled

This commit is contained in:
Jesús Pérez 2025-12-24 03:25:40 +00:00
parent d7be896713
commit d5a03183e9
Signed by: jesus
GPG Key ID: 9F243E355E0BC939
16 changed files with 1459 additions and 2 deletions

42
.github/renovate.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"packageRules": [
{
"matchManagers": ["cargo"],
"groupName": "Rust dependencies",
"schedule": ["before 3am on Monday"]
},
{
"matchManagers": ["npm"],
"groupName": "npm dependencies",
"schedule": ["before 3am on Monday"]
},
{
"matchUpdateTypes": ["minor", "patch"],
"groupName": "all non-major dependencies",
"groupSlug": "all-minor-patch",
"automerge": true
},
{
"matchDepTypes": ["devDependencies"],
"automerge": true
}
],
"rust": {
"enabled": true
},
"lockFileMaintenance": {
"enabled": true,
"schedule": ["before 3am on the first day of the month"]
},
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"]
},
"labels": ["dependencies"],
"prConcurrentLimit": 3,
"prHourlyLimit": 2
}

133
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,133 @@
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint:
name: Lint (${{ matrix.linter }})
runs-on: ubuntu-latest
strategy:
matrix:
linter: [rust, bash, nickel, nushell, markdown]
steps:
- uses: actions/checkout@v4
- name: Install just
uses: extractions/setup-just@v1
- name: Install Rust toolchain
if: matrix.linter == 'rust'
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Install shellcheck
if: matrix.linter == 'bash'
run: sudo apt-get install -y shellcheck
- name: Install Nickel
if: matrix.linter == 'nickel'
run: |
cargo install nickel-lang-cli
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Nushell
if: matrix.linter == 'nushell'
run: |
cargo install nu
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install markdownlint
if: matrix.linter == 'markdown'
run: npm install -g markdownlint-cli2
- name: Run linter
run: just dev::lint-${{ matrix.linter }}
coverage:
name: Code Coverage
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- uses: extractions/setup-just@v1
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate coverage
run: just dev::coverage-ci
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov.info
fail_ci_if_error: true
test:
name: Test (${{ matrix.os }})
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
needs: lint
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v1
- name: Run tests
run: just ci::test-all
build:
name: Build (${{ matrix.os }})
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
needs: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v1
- name: Build release
run: just ci::build-release
benchmark:
name: Benchmark
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: extractions/setup-just@v1
- name: Run benchmarks
run: just dev::bench
security:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-audit
run: cargo install cargo-audit --locked
- name: Run security audit
run: cargo audit
compliance:
name: License Compliance
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install cargo-deny
run: cargo install cargo-deny --locked
- name: Check licenses
run: cargo deny check licenses

263
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,263 @@
name: Release
on:
push:
tags:
- 'v*'
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.extract_version.outputs.version }}
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Extract version from tag
id: extract_version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.extract_version.outputs.version }}
draft: false
prerelease: false
build-release:
name: Build ${{ matrix.target }}
needs: create-release
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
use_cross: false
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
use_cross: true
- os: macos-latest
target: x86_64-apple-darwin
use_cross: false
- os: macos-latest
target: aarch64-apple-darwin
use_cross: false
- os: windows-latest
target: x86_64-pc-windows-msvc
use_cross: false
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install cross
if: matrix.use_cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Build release
shell: bash
run: |
if [ "${{ matrix.use_cross }}" = "true" ]; then
cross build --release --target ${{ matrix.target }} --locked
else
cargo build --release --target ${{ matrix.target }} --locked
fi
- name: Create distribution directory
shell: bash
run: |
VERSION="${{ needs.create-release.outputs.version }}"
TARGET="${{ matrix.target }}"
DIST_DIR="typedialog-${VERSION}-${TARGET}"
mkdir -p "$DIST_DIR"
# Copy binary
if [ "${{ matrix.os }}" = "windows-latest" ]; then
cp target/${TARGET}/release/typedialog.exe "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-tui.exe "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-web.exe "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-ag.exe "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-ag-server.exe "$DIST_DIR/"
else
cp target/${TARGET}/release/typedialog "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-tui "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-web "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-ag "$DIST_DIR/"
cp target/${TARGET}/release/typedialog-ag-server "$DIST_DIR/"
fi
# Copy README and LICENSE
cp README.md "$DIST_DIR/" 2>/dev/null || true
cp LICENSE "$DIST_DIR/" 2>/dev/null || true
echo "DIST_DIR=$DIST_DIR" >> $GITHUB_ENV
- name: Create tarball
shell: bash
run: |
if [ "${{ matrix.os }}" = "windows-latest" ]; then
7z a -tzip "${DIST_DIR}.zip" "${DIST_DIR}"
echo "ASSET=${DIST_DIR}.zip" >> $GITHUB_ENV
else
tar czf "${DIST_DIR}.tar.gz" "${DIST_DIR}"
echo "ASSET=${DIST_DIR}.tar.gz" >> $GITHUB_ENV
fi
- name: Generate SHA256 checksum
shell: bash
run: |
if [ "${{ matrix.os }}" = "windows-latest" ]; then
certutil -hashfile "${ASSET}" SHA256 > "${ASSET}.sha256"
else
shasum -a 256 "${ASSET}" > "${ASSET}.sha256"
fi
- name: Upload release asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.ASSET }}
asset_name: ${{ env.ASSET }}
asset_content_type: application/gzip
- name: Upload checksum
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.ASSET }}.sha256
asset_name: ${{ env.ASSET }}.sha256
asset_content_type: text/plain
generate-sbom:
name: Generate SBOM
needs: create-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-sbom
run: cargo install cargo-sbom --locked
- name: Generate SPDX SBOM
run: cargo sbom --output-format spdx_json_2_3 > sbom-spdx.json
- name: Generate CycloneDX SBOM
run: cargo sbom --output-format cyclone_dx_json_1_4 > sbom-cyclonedx.json
- name: Upload SPDX SBOM
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: sbom-spdx.json
asset_name: sbom-spdx.json
asset_content_type: application/json
- name: Upload CycloneDX SBOM
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: sbom-cyclonedx.json
asset_name: sbom-cyclonedx.json
asset_content_type: application/json
publish-crates:
name: Publish to crates.io
needs: [create-release, build-release, generate-sbom]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Verify version matches tag
run: |
TAG_VERSION="${{ needs.create-release.outputs.version }}"
CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "typedialog-core") | .version')
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
echo "Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)"
exit 1
fi
- name: Publish typedialog-core
run: cargo publish -p typedialog-core --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-ag-core
run: cargo publish -p typedialog-ag-core --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-ai
run: cargo publish -p typedialog-ai --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-prov-gen
run: cargo publish -p typedialog-prov-gen --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog
run: cargo publish -p typedialog --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-tui
run: cargo publish -p typedialog-tui --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-web
run: cargo publish -p typedialog-web --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-ag
run: cargo publish -p typedialog-ag --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Wait for crates.io
run: sleep 30
- name: Publish typedialog-ag-server
run: cargo publish -p typedialog-ag-server --token ${{ secrets.CARGO_REGISTRY_TOKEN }}

6
.gitignore vendored
View File

@ -2,7 +2,7 @@ CLAUDE.md
.claude
utils/save*sh
COMMIT_MESSAGE.md
wrks
.wrks
nushell
nushell-*
*.tar.gz
@ -61,3 +61,7 @@ cscope.*
# generated by verify-vendor.sh
vendordiff.patch
.claude/settings.local.json
# Generated SBOM files
SBOM.*.json
*.sbom.json

43
.woodpecker/Dockerfile Normal file
View File

@ -0,0 +1,43 @@
# Custom Docker image for Woodpecker CI
# Pre-installs common tools to speed up CI runs
#
# Build: docker build -t your-registry/typedialog-ci:latest -f .woodpecker/Dockerfile .
# Push: docker push your-registry/typedialog-ci:latest
#
# Then update .woodpecker/ci.yml to use: image: your-registry/typedialog-ci:latest
FROM rust:latest
# Install system dependencies
RUN apt-get update && apt-get install -y \
shellcheck \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Install just
RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
# Install Rust components
RUN rustup component add clippy rustfmt
# Install Rust tools (pre-compiled to speed up CI)
RUN cargo install \
cargo-audit \
cargo-deny \
cargo-sbom \
nickel-lang-cli \
nu \
--locked
# Set working directory
WORKDIR /workspace
# Verify installations
RUN just --version && \
cargo --version && \
cargo audit --version && \
cargo deny --version && \
cargo sbom --version && \
nickel --version && \
nu --version

View File

@ -0,0 +1,64 @@
# Ultra-optimized Alpine-based image for Woodpecker CI
# Size: ~400-500MB (vs 2.47GB original)
#
# Build: docker build -t your-registry/typedialog-ci:alpine -f .woodpecker/Dockerfile.alpine .
# Push: docker push your-registry/typedialog-ci:alpine
# Stage 1: Builder
FROM rust:1.92-alpine AS builder
# Install build dependencies
RUN apk add --no-cache \
musl-dev \
openssl-dev \
openssl-libs-static \
pkgconfig
# Set static linking for smaller binaries
ENV RUSTFLAGS="-C target-feature=+crt-static"
# Install Rust tools
RUN cargo install \
cargo-audit \
cargo-deny \
cargo-sbom \
nickel-lang-cli \
nu \
--locked
# Stage 2: Final image
FROM rust:1.92-alpine
# Install runtime dependencies
RUN apk add --no-cache \
shellcheck \
git \
curl \
ca-certificates \
openssl \
libgcc
# Install just
RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | sh -s -- --to /usr/local/bin
# Install Rust components
RUN rustup component add clippy rustfmt
# Copy compiled binaries from builder
COPY --from=builder /usr/local/cargo/bin/cargo-audit /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/cargo-deny /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/cargo-sbom /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/nickel /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/nu /usr/local/cargo/bin/
# Set working directory
WORKDIR /workspace
# Verify installations
RUN just --version && \
cargo --version && \
cargo audit --version && \
cargo deny --version && \
cargo sbom --version && \
nickel --version && \
nu --version

View File

@ -33,7 +33,7 @@ RUN cross build --target "${BUILD_TARGET}" --release
# Extract binaries to output directory
RUN mkdir -p /output/bin && \
for binary in typedialog typedialog-tui typedialog-web; do \
for binary in typedialog typedialog-tui typedialog-web typedialog-ai typedialog-ag typedialog-ag-server typedialog-prov-gen; do \
if [ -f "target/${BUILD_TARGET}/release/${binary}" ]; then \
cp "target/${BUILD_TARGET}/release/${binary}" /output/bin/ || \
cp "target/${BUILD_TARGET}/release/${binary}.exe" /output/bin/; \

View File

@ -0,0 +1,53 @@
# Minimal Docker image for Woodpecker CI (without Nushell)
# Build time: ~2 min
# Size: ~1.6GB (vs 1.68GB with nu, vs 2.47GB original)
#
# Build: docker build -t your-registry/typedialog-ci:minimal -f .woodpecker/Dockerfile.minimal .
FROM rust:1.92-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
shellcheck \
curl \
git \
ca-certificates \
wget \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
# Install Rust components
RUN rustup component add clippy rustfmt
# Install just (pre-compiled)
RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
# Install cargo-binstall for faster binary installations
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
# Install Rust tools using pre-compiled binaries (much faster, smaller)
RUN cargo binstall --no-confirm \
cargo-audit \
cargo-deny \
cargo-sbom
# Install nickel only (skip nu to save 46MB + build time)
RUN cargo install --locked nickel-lang-cli
# Clean up cargo cache to reduce size
RUN rm -rf /usr/local/cargo/registry \
&& rm -rf /usr/local/cargo/git
# Set working directory
WORKDIR /workspace
# Verify installations
RUN just --version && \
cargo --version && \
cargo audit --version && \
cargo deny --version && \
cargo sbom --version && \
nickel --version
# Note: nu (Nushell) NOT installed to save space
# To use this image, disable lint-nushell in .woodpecker/ci.yml

View File

@ -0,0 +1,60 @@
# Optimized Docker image for Woodpecker CI
# Reduces size from 2.47GB to ~800MB using multi-stage build
#
# Build: docker build -t your-registry/typedialog-ci:latest -f .woodpecker/Dockerfile.optimized .
# Push: docker push your-registry/typedialog-ci:latest
# Stage 1: Builder - compile Rust tools
FROM rust:1.92-slim AS builder
# Install only build dependencies
RUN apt-get update && apt-get install -y \
pkg-config \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Rust tools
RUN cargo install \
cargo-audit \
cargo-deny \
cargo-sbom \
nickel-lang-cli \
nu \
--locked
# Stage 2: Final image - copy only binaries
FROM rust:1.92-slim
# Install runtime dependencies only
RUN apt-get update && apt-get install -y \
shellcheck \
curl \
git \
ca-certificates \
libssl3 \
&& rm -rf /var/lib/apt/lists/*
# Install just (pre-compiled binary)
RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
# Install Rust components
RUN rustup component add clippy rustfmt
# Copy compiled binaries from builder stage
COPY --from=builder /usr/local/cargo/bin/cargo-audit /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/cargo-deny /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/cargo-sbom /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/nickel /usr/local/cargo/bin/
COPY --from=builder /usr/local/cargo/bin/nu /usr/local/cargo/bin/
# Set working directory
WORKDIR /workspace
# Verify installations
RUN just --version && \
cargo --version && \
cargo audit --version && \
cargo deny --version && \
cargo sbom --version && \
nickel --version && \
nu --version

View File

@ -0,0 +1,54 @@
# Ultra-fast image using pre-compiled binaries where possible
# Build time: ~2-3 min (vs ~20 min compiling from source)
# Size: ~600-700MB
#
# Build: docker build -t your-registry/typedialog-ci:prebuilt -f .woodpecker/Dockerfile.prebuilt .
FROM rust:1.92-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
shellcheck \
curl \
git \
ca-certificates \
wget \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
# Install Rust components
RUN rustup component add clippy rustfmt
# Install just (pre-compiled)
RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
# Install cargo-binstall for faster binary installations
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
# Install Rust tools using pre-compiled binaries (much faster, smaller)
RUN cargo binstall --no-confirm \
cargo-audit \
cargo-deny \
cargo-sbom
# These need to be compiled (no pre-built binaries available)
RUN cargo install --locked nickel-lang-cli nu
# Clean up cargo cache to reduce size
RUN rm -rf /usr/local/cargo/registry \
&& rm -rf /usr/local/cargo/git
# Set working directory
WORKDIR /workspace
# Verify installations
RUN just --version && \
cargo --version && \
cargo audit --version && \
cargo deny --version && \
cargo sbom --version && \
nickel --version && \
nu --version
# Show final size breakdown
RUN du -sh /usr/local/cargo/bin/*

238
.woodpecker/README.md Normal file
View File

@ -0,0 +1,238 @@
# Woodpecker CI Configuration
Pipelines for Gitea/Forgejo + Woodpecker CI.
## Files
### CI Pipeline
- **`ci.yml`** - Main CI pipeline (push, pull requests)
- **`ci-advanced.yml`** - Advanced CI with multi-OS matrix, coverage, benchmarks
### Release Pipelines
- **`release.yml`** - Basic release (Linux only, no Docker)
- **`release-advanced.yml`** - Advanced release (multi-OS, Gitea API, auto-upload)
- **`release-docker.yml`** - Docker-based release (uses .woodpecker/Dockerfile.cross)
### Docker Images
**CI Images** (pre-install tools, speed up CI ~5min):
- **`Dockerfile`** - Original (2.47GB, ❌ too large)
- **`Dockerfile.optimized`** - Multi-stage build (~800MB)
- **`Dockerfile.alpine`** - Alpine-based (~400MB, smallest)
- **`Dockerfile.prebuilt`** - Pre-compiled binaries (1.68GB, 3min build, **recommended**)
- **`Dockerfile.minimal`** - Prebuilt without nu (~1.6GB, 2min build, fastest)
**Build Images**:
- **`Dockerfile.cross`** - Cross-compilation image for multi-platform builds
## Setup
### 1. Activate Woodpecker CI
Enable Woodpecker CI in your Gitea/Forgejo repository settings.
### 2. (Optional) Build Custom Image
Speeds up CI by pre-installing tools (~5 min faster per run).
**Five Dockerfile options available**:
| Dockerfile | Size | Build Time | Tools | Use Case |
|------------|------|------------|-------|----------|
| `Dockerfile` | **2.47GB** | ~20 min | All + cargo cache | ❌ Too large (original) |
| `Dockerfile.optimized` | **~800MB** | ~15 min | All (multi-stage) | ✅ Multi-stage, Debian-based |
| `Dockerfile.alpine` | **~400MB** | ~12 min | All (Alpine) | ✅ Alpine-based, smallest |
| `Dockerfile.prebuilt` | **1.68GB** | **~3 min** | All (cargo-binstall) | ✅ Pre-compiled binaries, **fastest** |
| `Dockerfile.minimal` | **~1.6GB** | **~2 min** | All except nu | ✅ Fastest + smallest (no nushell lint) |
**Recommended: Use `Dockerfile.prebuilt`** (fastest builds, all tools)
**For minimal size**: Use `Dockerfile.minimal` if you can skip nushell linting (saves 46MB + 1min build)
```bash
# Build optimized image (choose one)
docker build -t your-registry/typedialog-ci:latest -f .woodpecker/Dockerfile.prebuilt .
# Or for smallest size (Alpine)
docker build -t your-registry/typedialog-ci:alpine -f .woodpecker/Dockerfile.alpine .
# Push to your registry
docker push your-registry/typedialog-ci:latest
# Update .woodpecker/ci.yml and ci-advanced.yml
# Change: image: rust:latest
# To: image: your-registry/typedialog-ci:latest
```
**Size comparison breakdown**:
```
Original (Dockerfile):
Base Debian + buildtools: 1.6GB
Rust toolchain: 538MB
cargo install (5 tools): 823MB (includes cargo cache)
--------------------------------
TOTAL: 2.47GB
Optimized (Dockerfile.prebuilt):
Base Rust slim: 400MB
Runtime deps: 60MB
Binaries only: 140MB (no cargo cache)
--------------------------------
TOTAL: ~600MB
```
### 3. Secrets Configuration
Configure these secrets in Gitea/Forgejo repository settings:
- `GITEA_TOKEN` - Gitea/Forgejo API token (for auto-creating releases)
- `CARGO_TOKEN` - crates.io token (optional, for publishing to crates.io)
- `SONAR_TOKEN` - SonarQube token (optional, for ci-advanced.yml coverage)
**To create a Gitea token**:
1. Go to Settings → Applications → Manage Access Tokens
2. Create token with scopes: `write:repository`, `write:issue`
3. Add as secret `GITEA_TOKEN` in repository settings
### 4. Docker Access (for release-docker.yml)
The `release-docker.yml` pipeline requires access to Docker socket. Configure in Woodpecker server:
**Option A: Privileged mode** (simpler, less secure):
```yaml
# In Woodpecker server config
WOODPECKER_BACKEND_DOCKER_ENABLE_PRIVILEGED: true
```
**Option B: Volume mount** (recommended):
Already configured in pipeline via:
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
Ensure Woodpecker agent has permission to access Docker socket.
## Pipelines
### CI Pipeline (`ci.yml`)
**Triggers**: Push to `main`/`develop`, Pull Requests
**Jobs**:
1. Lint (Rust, Bash, Nickel, Nushell, Markdown) - Parallel
2. Test (all features)
3. Build (release)
4. Security audit
5. License compliance check
**Duration**: ~15-20 minutes (without custom image), ~10-15 minutes (with custom image)
### Release Pipelines
All release pipelines trigger on Git tags `v*` (e.g., `v0.1.0`).
#### Option 1: Basic Release (`release.yml`)
**Use case**: Simple Linux-only releases, no Docker required
**Platforms**: x86_64-unknown-linux-gnu only
**Artifacts**:
- `typedialog-${VERSION}-x86_64-linux.tar.gz`
- `typedialog-${VERSION}-x86_64-linux.tar.gz.sha256`
- `sbom-spdx.json`, `sbom-cyclonedx.json`
**Duration**: ~20-25 minutes
#### Option 2: Advanced Release (`release-advanced.yml`)
**Use case**: Multi-platform builds with auto-upload to Gitea
**Platforms**: 5 targets (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64)
**Features**:
- Auto-creates Gitea release via API
- Matrix builds with `cross` tool
- Auto-uploads all artifacts to release
- Optional crates.io publishing
**Requirements**: `GITEA_TOKEN` secret
**Duration**: ~30-40 minutes (parallel builds)
#### Option 3: Docker-based Release (`release-docker.yml`)
**Use case**: Consistent builds using existing .woodpecker/Dockerfile.cross
**Platforms**: 5 targets (same as advanced)
**Features**:
- Uses project's `.woodpecker/Dockerfile.cross` for reproducible builds
- Includes BUILD_INFO.json manifest
- Auto-creates Gitea release and uploads artifacts
- Optional crates.io publishing
**Requirements**:
- `GITEA_TOKEN` secret
- Docker socket access (see Setup section)
**Duration**: ~35-45 minutes (includes Docker builds)
**Comparison**:
| Feature | Basic | Advanced | Docker-based |
|---------|-------|----------|--------------|
| Platforms | 1 (Linux x86_64) | 5 | 5 |
| Gitea API | ❌ Manual | ✅ Auto | ✅ Auto |
| Docker required | ❌ | ❌ | ✅ |
| Build method | cargo | cross CLI | .woodpecker/Dockerfile.cross |
| Consistency | Standard | Standard | High (containerized) |
| Manifest | ❌ | ❌ | ✅ BUILD_INFO.json |
## Differences from GitHub Actions
| Feature | GitHub Actions | Woodpecker CI |
|---------|---------------|---------------|
| Matrix builds | ✅ 3 OS | ❌ Linux only* |
| Coverage | ✅ Codecov | ❌ Not configured |
| Benchmarks | ✅ On PRs | ❌ Not configured |
| Caching | ✅ Built-in | ⚠️ Server-side** |
| SBOM | ✅ Auto-upload | ⚠️ Manual*** |
\* Multi-OS builds require multiple Woodpecker agents
\*\* Configure in Woodpecker server settings
\*\*\* Manual upload to Gitea/Forgejo releases
## Triggering Pipelines
```bash
# CI pipeline (automatic on push/PR)
git push origin main
# Release pipeline (manual tag)
git tag v0.1.0
git push origin v0.1.0
```
**Selecting Release Pipeline**:
By default, all three release pipelines will trigger on tags. To use only one:
1. **Rename the pipeline you want to use** to `release.yml`
2. **Rename others** to `.release-*.yml.disabled` (Woodpecker ignores these)
Example:
```bash
# Use Docker-based release only
mv .woodpecker/release.yml .woodpecker/.release-basic.yml.disabled
mv .woodpecker/release-advanced.yml .woodpecker/.release-advanced.yml.disabled
mv .woodpecker/release-docker.yml .woodpecker/release.yml
```
Or configure in Woodpecker UI to enable/disable specific pipelines.
## Viewing Results
- **Gitea/Forgejo**: Repository → Actions → Pipeline runs
- **Woodpecker UI**: https://your-woodpecker.instance/repos/{user}/{repo}

115
.woodpecker/ci-advanced.yml Normal file
View File

@ -0,0 +1,115 @@
# Advanced Woodpecker CI with self-hosted features
# Requires: Multiple agents (Linux/macOS/Windows), S3 cache, SonarQube
when:
event: [push, pull_request]
branch: [main, develop]
# === MATRIX BUILDS (Multi-OS) ===
# Requires Woodpecker agents on each platform
matrix:
PLATFORM:
- linux/amd64
- darwin/amd64
- windows/amd64
steps:
# Lint (parallel, Linux only for tools availability)
lint:
image: rust:latest
when:
matrix:
PLATFORM: linux/amd64
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- rustup component add clippy
- just ci::lint-all
cache:
- ~/.cargo/registry
- ~/.cargo/git
# Test (all platforms)
test:
image: rust:latest
platform: ${PLATFORM}
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- just ci::test-all
cache:
- ~/.cargo/registry
- ~/.cargo/git
- target/
depends_on:
- lint
# Coverage (Linux only, upload to SonarQube)
coverage:
image: rust:latest
when:
matrix:
PLATFORM: linux/amd64
secrets: [sonar_token]
commands:
- cargo install cargo-llvm-cov
- cargo llvm-cov --lcov --output-path lcov.info
- |
curl -sSL https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux.zip -o sonar.zip
unzip -q sonar.zip
sonar-scanner-5.0.1.3006-linux/bin/sonar-scanner \
-Dsonar.host.url=${SONAR_URL} \
-Dsonar.login=${SONAR_TOKEN} \
-Dsonar.projectKey=typedialog \
-Dsonar.sources=. \
-Dsonar.rust.lcov.reportPaths=lcov.info
depends_on:
- test
# Benchmark (on PRs, post results as comment)
benchmark:
image: rust:latest
when:
event: pull_request
matrix:
PLATFORM: linux/amd64
secrets: [gitea_token]
commands:
- cargo install cargo-criterion
- cargo criterion --message-format json > bench.json || true
- apk add --no-cache curl jq
- |
SUMMARY=$(cat bench.json | jq -r 'select(.reason=="benchmark-complete") | "\(.id): \(.mean.estimate)ns"' | head -10)
COMMENT="## 📊 Benchmark Results\n\`\`\`\n${SUMMARY}\n\`\`\`"
curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/issues/${CI_PULL_REQUEST}/comments" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\": \"${COMMENT}\"}"
depends_on:
- test
# Security audit (all platforms)
security:
image: rust:latest
platform: ${PLATFORM}
commands:
- cargo install cargo-audit --locked
- cargo audit
cache:
- ~/.cargo/bin
depends_on:
- lint
# Build (all platforms)
build:
image: rust:latest
platform: ${PLATFORM}
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- just ci::build-release
cache:
- ~/.cargo/registry
- ~/.cargo/git
- target/
depends_on:
- test
- security

89
.woodpecker/ci.yml Normal file
View File

@ -0,0 +1,89 @@
# Woodpecker CI Pipeline
# Equivalent to .github/workflows/ci.yml
when:
event: [push, pull_request, manual]
branch:
- main
- develop
steps:
# === LINTING ===
lint-rust:
image: rust:latest
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- rustup component add clippy
- just dev::lint-rust
lint-bash:
image: koalaman/shellcheck-alpine:stable
commands:
- apk add --no-cache curl bash
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- just dev::lint-bash
lint-nickel:
image: rust:latest
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- cargo install nickel-lang-cli
- just dev::lint-nickel
lint-nushell:
image: rust:latest
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- cargo install nu
- just dev::lint-nushell
lint-markdown:
image: node:alpine
commands:
- apk add --no-cache curl bash
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- npm install -g markdownlint-cli2
- just dev::lint-markdown
# === TESTING ===
test:
image: rust:latest
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- just ci::test-all
depends_on:
- lint-rust
- lint-bash
- lint-nickel
- lint-nushell
- lint-markdown
# === BUILD ===
build:
image: rust:latest
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- just ci::build-release
depends_on:
- test
# === SECURITY ===
security-audit:
image: rust:latest
commands:
- cargo install cargo-audit --locked
- cargo audit
depends_on:
- lint-rust
license-check:
image: rust:latest
commands:
- cargo install cargo-deny --locked
- cargo deny check licenses
depends_on:
- lint-rust

View File

@ -0,0 +1,115 @@
# Advanced Release Pipeline with auto-upload to Gitea/Forgejo
# Triggers on tags: v*
when:
event: tag
tag: v*
steps:
# Create GitHub-style release via Gitea API
create-release:
image: alpine:latest
secrets: [gitea_token]
commands:
- apk add --no-cache curl jq
- |
RELEASE_DATA=$(jq -n \
--arg tag "${CI_COMMIT_TAG}" \
--arg name "Release ${CI_COMMIT_TAG}" \
--arg body "Automated release from Woodpecker CI" \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
RELEASE_ID=$(curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "${RELEASE_DATA}" | jq -r '.id')
echo "RELEASE_ID=${RELEASE_ID}" >> /tmp/release.env
echo "✓ Created release ${CI_COMMIT_TAG} (ID: ${RELEASE_ID})"
# Build binaries for all platforms (matrix)
build-binaries:
image: rust:latest
matrix:
TARGET:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-pc-windows-msvc
commands:
- |
if [[ "${TARGET}" == *"aarch64"* || "${TARGET}" == *"windows"* ]]; then
cargo install cross --git https://github.com/cross-rs/cross
cross build --release --target ${TARGET} --locked
else
rustup target add ${TARGET}
cargo build --release --target ${TARGET} --locked
fi
- mkdir -p dist
- |
if [[ "${TARGET}" == *"windows"* ]]; then
cp target/${TARGET}/release/*.exe dist/ || true
else
cp target/${TARGET}/release/typedialog* dist/ || true
rm -f dist/*.d dist/*.rlib || true
fi
- tar czf typedialog-${CI_COMMIT_TAG}-${TARGET}.tar.gz -C dist .
- sha256sum typedialog-${CI_COMMIT_TAG}-${TARGET}.tar.gz > typedialog-${CI_COMMIT_TAG}-${TARGET}.tar.gz.sha256
depends_on:
- create-release
# Generate SBOMs
generate-sbom:
image: rust:latest
commands:
- cargo install cargo-sbom --locked
- cargo sbom --output-format spdx_json_2_3 > sbom-spdx.json
- cargo sbom --output-format cyclone_dx_json_1_4 > sbom-cyclonedx.json
depends_on:
- create-release
# Upload all artifacts to Gitea release
upload-artifacts:
image: alpine:latest
secrets: [gitea_token]
commands:
- apk add --no-cache curl jq
- source /tmp/release.env
- |
for file in typedialog-${CI_COMMIT_TAG}-*.tar.gz*; do
echo "Uploading ${file}..."
curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases/${RELEASE_ID}/assets?name=${file}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${file}"
done
- |
for file in sbom-*.json; do
echo "Uploading ${file}..."
curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases/${RELEASE_ID}/assets?name=${file}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary "@${file}"
done
- echo "✓ All artifacts uploaded successfully"
depends_on:
- build-binaries
- generate-sbom
# Optional: Publish to crates.io
publish-crates:
image: rust:latest
secrets: [cargo_token]
when:
# Only if CARGO_TOKEN secret is set
evaluate: 'CI_PIPELINE_EVENT == "tag" && CI_COMMIT_TAG =~ "^v[0-9]"'
commands:
- |
for crate in typedialog-core typedialog-ag-core typedialog-ai typedialog-prov-gen typedialog typedialog-tui typedialog-web typedialog-ag typedialog-ag-server; do
echo "Publishing ${crate}..."
cargo publish -p ${crate} --token ${CARGO_TOKEN} || true
sleep 30
done
depends_on:
- upload-artifacts

View File

@ -0,0 +1,131 @@
# Release Pipeline using Dockerfile.cross
# Uses existing Dockerfile.cross for consistent multi-platform builds
# Triggers on tags: v*
when:
event: tag
tag: v*
steps:
# Create GitHub-style release via Gitea API
create-release:
image: alpine:latest
secrets: [gitea_token]
commands:
- apk add --no-cache curl jq
- |
RELEASE_DATA=$(jq -n \
--arg tag "${CI_COMMIT_TAG}" \
--arg name "Release ${CI_COMMIT_TAG}" \
--arg body "Multi-platform build using Dockerfile.cross" \
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
RELEASE_ID=$(curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "${RELEASE_DATA}" | jq -r '.id')
echo "RELEASE_ID=${RELEASE_ID}" > /tmp/release.env
echo "✓ Created release ${CI_COMMIT_TAG} (ID: ${RELEASE_ID})"
# Build binaries using Dockerfile.cross (matrix)
build-binaries:
image: docker:27-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
matrix:
TARGET:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- x86_64-apple-darwin
- aarch64-apple-darwin
- x86_64-pc-windows-msvc
commands:
- echo "Building for target ${TARGET}..."
# Build using Dockerfile.cross
- |
docker build -t typedialog-build:${TARGET} \
--build-arg TARGET=${TARGET} \
-f .woodpecker/Dockerfile.cross .
# Create container and extract binaries
- docker create --name extract-${TARGET} typedialog-build:${TARGET}
- mkdir -p dist-${TARGET}
- docker cp extract-${TARGET}:/output/bin/. dist-${TARGET}/
- docker cp extract-${TARGET}:/output/BUILD_INFO.json dist-${TARGET}/ || true
- docker rm extract-${TARGET}
# Package binaries
- tar czf typedialog-${CI_COMMIT_TAG}-${TARGET}.tar.gz -C dist-${TARGET} .
- sha256sum typedialog-${CI_COMMIT_TAG}-${TARGET}.tar.gz > typedialog-${CI_COMMIT_TAG}-${TARGET}.tar.gz.sha256
- echo "✓ Built and packaged ${TARGET}"
depends_on:
- create-release
# Generate SBOMs (SPDX + CycloneDX)
generate-sbom:
image: typedialog-ci:latest # Uses custom image with cargo-sbom pre-installed
commands:
- cargo sbom --output-format spdx_json_2_3 > sbom-spdx.json
- cargo sbom --output-format cyclone_dx_json_1_4 > sbom-cyclonedx.json
- echo "✓ Generated SBOMs"
depends_on:
- create-release
# Upload all artifacts to Gitea release
upload-artifacts:
image: alpine:latest
secrets: [gitea_token]
commands:
- apk add --no-cache curl jq
- source /tmp/release.env
- echo "Uploading to release ID ${RELEASE_ID}..."
# Upload binary archives and checksums
- |
for file in typedialog-${CI_COMMIT_TAG}-*.tar.gz*; do
if [ -f "${file}" ]; then
echo "Uploading ${file}..."
curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases/${RELEASE_ID}/assets?name=${file}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary "@${file}"
fi
done
# Upload SBOMs
- |
for file in sbom-*.json; do
if [ -f "${file}" ]; then
echo "Uploading ${file}..."
curl -X POST "${GITEA_URL}/api/v1/repos/${CI_REPO}/releases/${RELEASE_ID}/assets?name=${file}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
--data-binary "@${file}"
fi
done
- echo "✓ All artifacts uploaded successfully"
depends_on:
- build-binaries
- generate-sbom
# Optional: Publish to crates.io
publish-crates:
image: rust:latest
secrets: [cargo_token]
when:
# Only if CARGO_TOKEN secret is set
evaluate: 'CI_PIPELINE_EVENT == "tag" && CI_COMMIT_TAG =~ "^v[0-9]"'
commands:
- |
for crate in typedialog-core typedialog-ag-core typedialog-ai typedialog-prov-gen typedialog typedialog-tui typedialog-web typedialog-ag typedialog-ag-server; do
echo "Publishing ${crate}..."
cargo publish -p ${crate} --token ${CARGO_TOKEN} || true
sleep 30
done
- echo "✓ Published to crates.io"
depends_on:
- upload-artifacts

53
.woodpecker/release.yml Normal file
View File

@ -0,0 +1,53 @@
# Woodpecker CI Release Pipeline
# Equivalent to .github/workflows/release.yml
# Triggered on git tags (v*)
when:
event: tag
tag: v*
steps:
# === BUILD RELEASE ===
build-release:
image: rust:latest
commands:
- curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin
- cargo build --release --locked
- mkdir -p dist
- cp target/release/typedialog dist/
- cp target/release/typedialog-tui dist/
- cp target/release/typedialog-web dist/
- cp target/release/typedialog-ag dist/
- cp target/release/typedialog-ag-server dist/
- cp README.md dist/ 2>/dev/null || true
- cp LICENSE dist/ 2>/dev/null || true
- cd dist && tar czf ../typedialog-${CI_COMMIT_TAG}-x86_64-linux.tar.gz .
- cd .. && sha256sum typedialog-${CI_COMMIT_TAG}-x86_64-linux.tar.gz > typedialog-${CI_COMMIT_TAG}-x86_64-linux.tar.gz.sha256
# === GENERATE SBOM ===
generate-sbom:
image: rust:latest
commands:
- cargo install cargo-sbom --locked
- cargo sbom --output-format spdx_json_2_3 > sbom-spdx.json
- cargo sbom --output-format cyclone_dx_json_1_4 > sbom-cyclonedx.json
depends_on:
- build-release
# === PUBLISH ARTIFACTS ===
# Note: This requires Woodpecker server configured with Forgejo/Gitea integration
# Artifacts will be available in the pipeline artifacts section
publish-artifacts:
image: alpine:latest
commands:
- echo "✓ Release artifacts generated successfully"
- echo "📦 Binary: typedialog-${CI_COMMIT_TAG}-x86_64-linux.tar.gz"
- echo "🔐 Checksum: typedialog-${CI_COMMIT_TAG}-x86_64-linux.tar.gz.sha256"
- echo "📋 SBOM (SPDX): sbom-spdx.json"
- echo "📋 SBOM (CycloneDX): sbom-cyclonedx.json"
- ls -lh *.tar.gz *.sha256 *.json
depends_on:
- generate-sbom