diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..140c931 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI +run-name: "CI" + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... + + - name: Format check + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "Files need gofmt:" + echo "$unformatted" + exit 1 + fi + + build: + name: Build + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build binary + run: | + CGO_ENABLED=0 go build -trimpath -o pgtidy ./cmd/pgtidy + echo "Build successful:" + ls -lh pgtidy diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..23f2828 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,376 @@ +name: Release +run-name: "Release ${{ github.event.inputs.tag || github.ref_name }}" + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to release (e.g. v0.1.0)' + required: true + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... + + release: + name: Release + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build release binaries + run: | + VERSION="${{ github.event.inputs.tag || github.ref_name }}" + for target in "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64"; do + GOOS="${target%/*}" + GOARCH="${target#*/}" + EXT="" + [ "$GOOS" = "windows" ] && EXT=".exe" + NAME="pgtidy-${GOOS}-${GOARCH}${EXT}" + GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags "-X main.version=${VERSION}" \ + -o "$NAME" ./cmd/pgtidy + echo "Built $NAME ($(ls -lh "$NAME" | awk '{print $5}'))" + done + + - name: Create release and upload binaries + run: | + set -euo pipefail + TAG="${{ github.event.inputs.tag || github.ref_name }}" + API="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases" + + PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${TAG}$" | head -1) + if [ -n "$PREV_TAG" ]; then + RANGE="${PREV_TAG}..${TAG}" + else + RANGE="HEAD~20..HEAD" + fi + NOTES=$(git log "$RANGE" --pretty=format:"- %s" --no-merges) + BODY="## What's changed"$'\n'"${NOTES}" + BODY_JSON=$(printf '%s' "$BODY" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))') + + RELEASE=$(curl -s -X POST "$API" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\",\"body\":${BODY_JSON},\"draft\":true}") + + UPLOAD_URL=$(echo "$RELEASE" | python3 -c "import json,sys,re; r=json.load(sys.stdin); print(re.sub(r'\{[^}]*\}','',r['upload_url']))") + if [ -z "$UPLOAD_URL" ]; then + echo "Failed to create release: $RELEASE" + exit 1 + fi + + echo "$UPLOAD_URL" > /tmp/upload_url.txt + + for f in pgtidy-*; do + echo "Uploading $f..." + curl -s -X POST "${UPLOAD_URL}?name=${f}" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@${f}" > /dev/null + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/upload-artifact@v4 + with: + name: upload-url + path: /tmp/upload_url.txt + retention-days: 1 + + pkg-deb: + name: Debian packages + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build .deb packages + run: | + VERSION="${{ github.event.inputs.tag || github.ref_name }}" + PKGVER="${VERSION#v}" + + for GOARCH in amd64 arm64; do + GOOS=linux GOARCH=$GOARCH CGO_ENABLED=0 go build \ + -trimpath \ + -ldflags "-X main.version=${PKGVER}" \ + -o pgtidy ./cmd/pgtidy + + PKGDIR="pgtidy_${PKGVER}_${GOARCH}" + mkdir -p "${PKGDIR}/DEBIAN" "${PKGDIR}/usr/bin" + install -m755 pgtidy "${PKGDIR}/usr/bin/pgtidy" + sed -e "s/VERSION/${PKGVER}/" -e "s/ARCH/${GOARCH}/" \ + linux/debian/control > "${PKGDIR}/DEBIAN/control" + dpkg-deb --build --root-owner-group "${PKGDIR}" + echo "Built ${PKGDIR}.deb" + rm -rf "${PKGDIR}" pgtidy + done + + - name: Upload to release + run: | + set -euo pipefail + TAG="${{ github.event.inputs.tag || github.ref_name }}" + RELEASE=$(curl -s "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \ + -H "Authorization: token ${GITHUB_TOKEN}") + UPLOAD_URL=$(echo "$RELEASE" | python3 -c "import json,sys,re; r=json.load(sys.stdin); print(re.sub(r'\{[^}]*\}','',r['upload_url']))") + for f in *.deb; do + echo "Uploading $(basename "$f")..." + curl -s -X POST "${UPLOAD_URL}?name=$(basename "$f")" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/vnd.debian.binary-package" \ + --data-binary "@${f}" > /dev/null + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + pkg-rpm: + name: RPM package + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build RPM + run: | + set -euo pipefail + VERSION="${{ github.event.inputs.tag || github.ref_name }}" + PKGVER="${VERSION#v}" + GO_VER="$(awk '/^go / { print $2; exit }' go.mod)" + + git archive --format=tar.gz --prefix=pgtidy-${PKGVER}/ HEAD \ + > pgtidy-${PKGVER}.tar.gz + + sed -i "s/^Version:.*/Version: ${PKGVER}/" linux/centos/pgtidy.spec + + CID=$(docker create \ + -e GO_VER="${GO_VER}" \ + -e PKGVER="${PKGVER}" \ + -w /build \ + rockylinux:9 \ + bash -lc " + set -euo pipefail + dnf install -y rpm-build git && + curl -fsSL https://go.dev/dl/go\${GO_VER}.linux-amd64.tar.gz | tar -C /usr/local -xz && + export PATH=\$PATH:/usr/local/go/bin && + mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} && + cp pgtidy-${PKGVER}.tar.gz ~/rpmbuild/SOURCES/ && + cp linux/centos/pgtidy.spec ~/rpmbuild/SPECS/ && + rpmbuild --nodeps -ba ~/rpmbuild/SPECS/pgtidy.spec + ") + + cleanup() { docker rm -f "$CID" >/dev/null 2>&1 || true; } + trap cleanup EXIT + + docker cp pgtidy-${PKGVER}.tar.gz "$CID:/build/" + docker cp linux "$CID:/build/linux" + docker start -a "$CID" + mkdir -p rpm-out + docker cp "$CID:/root/rpmbuild/RPMS/." rpm-out/ + + trap - EXIT; cleanup + + - name: Upload to release + run: | + set -euo pipefail + TAG="${{ github.event.inputs.tag || github.ref_name }}" + RELEASE=$(curl -s "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \ + -H "Authorization: token ${GITHUB_TOKEN}") + UPLOAD_URL=$(echo "$RELEASE" | python3 -c "import json,sys,re; r=json.load(sys.stdin); print(re.sub(r'\{[^}]*\}','',r['upload_url']))") + while IFS= read -r f; do + echo "Uploading $(basename "$f")..." + curl -s -X POST "${UPLOAD_URL}?name=$(basename "$f")" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/x-rpm" \ + --data-binary "@${f}" > /dev/null + done < <(find rpm-out -name "*.rpm") + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + pkg-aur: + name: AUR package + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Publish to AUR + env: + AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} + run: | + set -euo pipefail + VERSION="${{ github.event.inputs.tag || github.ref_name }}" + PKGVER="${VERSION#v}" + AUR_KEY_PATH="$HOME/.ssh/aur" + AUR_KNOWN_HOSTS="$HOME/.ssh/known_hosts" + + mkdir -p ~/.ssh && chmod 700 ~/.ssh + + if [ -z "${AUR_SSH_KEY:-}" ]; then + echo "AUR_SSH_KEY is empty — skipping AUR publish" + exit 0 + fi + + CLEAN_AUR_SSH_KEY="$(printf '%s' "$AUR_SSH_KEY" | tr -d '\r')" + if printf '%s' "$CLEAN_AUR_SSH_KEY" | grep -q "^-----BEGIN .*PRIVATE KEY-----$"; then + printf '%s\n' "$CLEAN_AUR_SSH_KEY" > "$AUR_KEY_PATH" + elif printf '%s' "$CLEAN_AUR_SSH_KEY" | grep -q '\\n'; then + printf '%b\n' "$CLEAN_AUR_SSH_KEY" > "$AUR_KEY_PATH" + else + if ! printf '%s' "$CLEAN_AUR_SSH_KEY" | tr -d '[:space:]' | base64 --decode > "$AUR_KEY_PATH" 2>/dev/null; then + printf '%s\n' "$CLEAN_AUR_SSH_KEY" > "$AUR_KEY_PATH" + fi + fi + chmod 600 "$AUR_KEY_PATH" + + if ! ssh-keygen -y -f "$AUR_KEY_PATH" >/dev/null 2>&1; then + echo "AUR_SSH_KEY is not a valid private key — skipping AUR publish" + exit 0 + fi + + ssh-keyscan -t rsa,ed25519 aur.archlinux.org >> "$AUR_KNOWN_HOSTS" + chmod 644 "$AUR_KNOWN_HOSTS" + + GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$AUR_KNOWN_HOSTS -i $AUR_KEY_PATH" \ + git clone ssh://aur@aur.archlinux.org/pgtidy-bin.git aur-repo + + CURRENT_PKGVER=$(awk -F= '/^pkgver=/ {print $2; exit}' aur-repo/PKGBUILD | tr -d "[:space:]") + CURRENT_PKGREL=$(awk -F= '/^pkgrel=/ {print $2; exit}' aur-repo/PKGBUILD | tr -d "[:space:]") + + if [ "$CURRENT_PKGVER" = "$PKGVER" ]; then + PKGREL=$((CURRENT_PKGREL + 1)) + else + PKGREL=1 + fi + + echo "Publishing pgtidy-bin ${PKGVER}-${PKGREL} to AUR" + + SHA_AMD64=$(curl -fsSL "https://git.warky.dev/wdevs/pgtidy/releases/download/v${PKGVER}/pgtidy-linux-amd64" | sha256sum | cut -d' ' -f1) + SHA_ARM64=$(curl -fsSL "https://git.warky.dev/wdevs/pgtidy/releases/download/v${PKGVER}/pgtidy-linux-arm64" | sha256sum | cut -d' ' -f1) + + sed -e "s/^pkgver=.*/pkgver=${PKGVER}/" \ + -e "s/^pkgrel=.*/pkgrel=${PKGREL}/" \ + -e "s/^sha256sums_x86_64=.*/sha256sums_x86_64=('${SHA_AMD64}')/" \ + -e "s/^sha256sums_aarch64=.*/sha256sums_aarch64=('${SHA_ARM64}')/" \ + linux/arch/PKGBUILD > aur-repo/PKGBUILD + + CID=$(docker run -d archlinux:latest sleep infinity) + cleanup_docker() { docker rm -f "$CID" >/dev/null 2>&1 || true; } + trap cleanup_docker EXIT + + docker exec "$CID" mkdir -p /build + docker cp aur-repo/PKGBUILD "$CID:/build/PKGBUILD" + docker exec "$CID" bash -c " + pacman -Sy --noconfirm base-devel && + useradd -m builder && + chown -R builder:builder /build && + runuser -u builder -- bash -c 'cd /build && makepkg --printsrcinfo > .SRCINFO' + " + docker cp "$CID:/build/.SRCINFO" aur-repo/.SRCINFO + + trap - EXIT; cleanup_docker + + cd aur-repo + git config user.email "hein@warky.dev" + git config user.name "Hein" + git add PKGBUILD .SRCINFO + git commit -m "Update to v${PKGVER}-${PKGREL}" + GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$AUR_KNOWN_HOSTS -i $AUR_KEY_PATH" \ + git push origin HEAD:master + + vscode-package: + name: VSCode Extension + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install and package + working-directory: editors/vscode + run: | + npm ci + npm run compile + npm run package + + - name: Upload to release + run: | + set -euo pipefail + TAG="${{ github.event.inputs.tag || github.ref_name }}" + RELEASE=$(curl -s "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \ + -H "Authorization: token ${GITHUB_TOKEN}") + UPLOAD_URL=$(echo "$RELEASE" | python3 -c "import json,sys,re; r=json.load(sys.stdin); print(re.sub(r'\{[^}]*\}','',r['upload_url']))") + for f in editors/vscode/*.vsix; do + echo "Uploading $(basename "$f")..." + curl -s -X POST "${UPLOAD_URL}?name=$(basename "$f")" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@${f}" > /dev/null + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + datagrip-package: + name: DataGrip Plugin + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + + - name: Build plugin + working-directory: editors/datagrip + run: ./gradlew buildPlugin + + - name: Upload to release + run: | + set -euo pipefail + TAG="${{ github.event.inputs.tag || github.ref_name }}" + RELEASE=$(curl -s "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \ + -H "Authorization: token ${GITHUB_TOKEN}") + UPLOAD_URL=$(echo "$RELEASE" | python3 -c "import json,sys,re; r=json.load(sys.stdin); print(re.sub(r'\{[^}]*\}','',r['upload_url']))") + for f in editors/datagrip/build/distributions/*.zip; do + echo "Uploading $(basename "$f")..." + curl -s -X POST "${UPLOAD_URL}?name=$(basename "$f")" \ + -H "Authorization: token ${GITHUB_TOKEN}" \ + -H "Content-Type: application/zip" \ + --data-binary "@${f}" > /dev/null + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}