From d5a03183e9a2dc75faba015e3139b73e046c5683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Wed, 24 Dec 2025 03:25:40 +0000 Subject: [PATCH] chore: add CI for github and woodpecker --- .github/renovate.json | 42 +++++ .github/workflows/ci.yml | 133 ++++++++++++++++ .github/workflows/release.yml | 263 +++++++++++++++++++++++++++++++ .gitignore | 6 +- .woodpecker/Dockerfile | 43 +++++ .woodpecker/Dockerfile.alpine | 64 ++++++++ .woodpecker/Dockerfile.cross | 2 +- .woodpecker/Dockerfile.minimal | 53 +++++++ .woodpecker/Dockerfile.optimized | 60 +++++++ .woodpecker/Dockerfile.prebuilt | 54 +++++++ .woodpecker/README.md | 238 ++++++++++++++++++++++++++++ .woodpecker/ci-advanced.yml | 115 ++++++++++++++ .woodpecker/ci.yml | 89 +++++++++++ .woodpecker/release-advanced.yml | 115 ++++++++++++++ .woodpecker/release-docker.yml | 131 +++++++++++++++ .woodpecker/release.yml | 53 +++++++ 16 files changed, 1459 insertions(+), 2 deletions(-) create mode 100644 .github/renovate.json create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .woodpecker/Dockerfile create mode 100644 .woodpecker/Dockerfile.alpine create mode 100644 .woodpecker/Dockerfile.minimal create mode 100644 .woodpecker/Dockerfile.optimized create mode 100644 .woodpecker/Dockerfile.prebuilt create mode 100644 .woodpecker/README.md create mode 100644 .woodpecker/ci-advanced.yml create mode 100644 .woodpecker/ci.yml create mode 100644 .woodpecker/release-advanced.yml create mode 100644 .woodpecker/release-docker.yml create mode 100644 .woodpecker/release.yml diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..ee08c2d --- /dev/null +++ b/.github/renovate.json @@ -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 +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9ae4942 --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3d04e9b --- /dev/null +++ b/.github/workflows/release.yml @@ -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 }} diff --git a/.gitignore b/.gitignore index a7def72..4cf1bb4 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/.woodpecker/Dockerfile b/.woodpecker/Dockerfile new file mode 100644 index 0000000..815eb56 --- /dev/null +++ b/.woodpecker/Dockerfile @@ -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 diff --git a/.woodpecker/Dockerfile.alpine b/.woodpecker/Dockerfile.alpine new file mode 100644 index 0000000..a8ad209 --- /dev/null +++ b/.woodpecker/Dockerfile.alpine @@ -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 diff --git a/.woodpecker/Dockerfile.cross b/.woodpecker/Dockerfile.cross index 59cf550..f5ca690 100644 --- a/.woodpecker/Dockerfile.cross +++ b/.woodpecker/Dockerfile.cross @@ -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/; \ diff --git a/.woodpecker/Dockerfile.minimal b/.woodpecker/Dockerfile.minimal new file mode 100644 index 0000000..b702296 --- /dev/null +++ b/.woodpecker/Dockerfile.minimal @@ -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 diff --git a/.woodpecker/Dockerfile.optimized b/.woodpecker/Dockerfile.optimized new file mode 100644 index 0000000..666a098 --- /dev/null +++ b/.woodpecker/Dockerfile.optimized @@ -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 diff --git a/.woodpecker/Dockerfile.prebuilt b/.woodpecker/Dockerfile.prebuilt new file mode 100644 index 0000000..d594a1a --- /dev/null +++ b/.woodpecker/Dockerfile.prebuilt @@ -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/* diff --git a/.woodpecker/README.md b/.woodpecker/README.md new file mode 100644 index 0000000..8c822d6 --- /dev/null +++ b/.woodpecker/README.md @@ -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} diff --git a/.woodpecker/ci-advanced.yml b/.woodpecker/ci-advanced.yml new file mode 100644 index 0000000..603b8ef --- /dev/null +++ b/.woodpecker/ci-advanced.yml @@ -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 diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml new file mode 100644 index 0000000..fe32e35 --- /dev/null +++ b/.woodpecker/ci.yml @@ -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 diff --git a/.woodpecker/release-advanced.yml b/.woodpecker/release-advanced.yml new file mode 100644 index 0000000..bbeef80 --- /dev/null +++ b/.woodpecker/release-advanced.yml @@ -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 diff --git a/.woodpecker/release-docker.yml b/.woodpecker/release-docker.yml new file mode 100644 index 0000000..0f99ed8 --- /dev/null +++ b/.woodpecker/release-docker.yml @@ -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 diff --git a/.woodpecker/release.yml b/.woodpecker/release.yml new file mode 100644 index 0000000..5df13f1 --- /dev/null +++ b/.woodpecker/release.yml @@ -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