diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml new file mode 100644 index 0000000..0d97f28 --- /dev/null +++ b/.forgejo/workflows/release.yml @@ -0,0 +1,203 @@ +name: Release + +on: + push: + tags: + - "v*" + +jobs: + release: + runs-on: ubuntu-latest + + env: + BINARY_NAME: xdebug-mcp + BUILD_PATH: build/xdebug-mcp-linux-amd64 + CHECKSUM_PATH: build/xdebug-mcp-linux-amd64.sha256 + MANIFEST_PATH: mcp.toml + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build linux amd64 binary + run: make build GOOS=linux GOARCH=amd64 + + - name: Generate binary checksum + run: | + set -euo pipefail + + asset_name="$(basename "${BUILD_PATH}")" + checksum_value="$(sha256sum "${BUILD_PATH}" | cut -d' ' -f1)" + printf '%s %s\n' "${checksum_value}" "${asset_name}" > "${CHECKSUM_PATH}" + + - name: Generate release notes + id: release_notes + env: + TAG_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [ -z "${TAG_NAME}" ]; then + echo "missing tag name" >&2 + exit 1 + fi + + previous_tag="$(git describe --tags --abbrev=0 "${TAG_NAME}^" 2>/dev/null || true)" + + { + printf 'body<> "${GITHUB_OUTPUT}" + + - name: Create or fetch release + id: release + env: + TAG_NAME: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} + API_URL: ${{ github.api_url }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + RELEASE_BODY: ${{ steps.release_notes.outputs.body }} + run: | + set -euo pipefail + + if [ -z "${TAG_NAME}" ]; then + echo "missing tag name" >&2 + exit 1 + fi + + owner="${REPOSITORY%%/*}" + repo="${REPOSITORY#*/}" + release_url="${API_URL}/repos/${owner}/${repo}/releases/tags/${TAG_NAME}" + create_url="${API_URL}/repos/${owner}/${repo}/releases" + payload="$(python3 -c 'import json, os; print(json.dumps({"tag_name": os.environ["TAG_NAME"], "name": os.environ["TAG_NAME"], "body": os.environ["RELEASE_BODY"], "prerelease": False, "draft": False}))')" + + http_code="$(curl -sS -o release.json -w '%{http_code}' \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "${release_url}")" + + if [ "${http_code}" = "404" ]; then + http_code="$(curl -sS -o release.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "${payload}" \ + "${create_url}")" + elif [ "${http_code}" -ge 200 ] && [ "${http_code}" -lt 300 ]; then + release_id="$(python3 -c 'import json; print(json.load(open("release.json", "r", encoding="utf-8"))["id"])')" + update_url="${API_URL}/repos/${owner}/${repo}/releases/${release_id}" + + http_code="$(curl -sS -o release.json -w '%{http_code}' \ + -X PATCH \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "${payload}" \ + "${update_url}")" + fi + + if [ "${http_code}" -lt 200 ] || [ "${http_code}" -ge 300 ]; then + echo "release API call failed with status ${http_code}" >&2 + cat release.json >&2 + exit 1 + fi + + release_id="$(python3 -c 'import json; print(json.load(open("release.json", "r", encoding="utf-8"))["id"])')" + + echo "release_id=${release_id}" >> "${GITHUB_OUTPUT}" + + - name: Upload release asset + env: + REPOSITORY: ${{ github.repository }} + API_URL: ${{ github.api_url }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + RELEASE_ID: ${{ steps.release.outputs.release_id }} + run: | + set -euo pipefail + + owner="${REPOSITORY%%/*}" + repo="${REPOSITORY#*/}" + asset_name="$(basename "${BUILD_PATH}")" + upload_url="${API_URL}/repos/${owner}/${repo}/releases/${RELEASE_ID}/assets?name=${asset_name}" + + http_code="$(curl -sS -o asset.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"${BUILD_PATH}" \ + "${upload_url}")" + + if [ "${http_code}" -lt 200 ] || [ "${http_code}" -ge 300 ]; then + echo "asset upload failed with status ${http_code}" >&2 + cat asset.json >&2 + exit 1 + fi + + - name: Upload manifest asset + env: + REPOSITORY: ${{ github.repository }} + API_URL: ${{ github.api_url }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + RELEASE_ID: ${{ steps.release.outputs.release_id }} + run: | + set -euo pipefail + + owner="${REPOSITORY%%/*}" + repo="${REPOSITORY#*/}" + asset_name="$(basename "${MANIFEST_PATH}")" + upload_url="${API_URL}/repos/${owner}/${repo}/releases/${RELEASE_ID}/assets?name=${asset_name}" + + http_code="$(curl -sS -o asset.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"${MANIFEST_PATH}" \ + "${upload_url}")" + + if [ "${http_code}" -lt 200 ] || [ "${http_code}" -ge 300 ]; then + echo "asset upload failed with status ${http_code}" >&2 + cat asset.json >&2 + exit 1 + fi + + - name: Upload checksum asset + env: + REPOSITORY: ${{ github.repository }} + API_URL: ${{ github.api_url }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + RELEASE_ID: ${{ steps.release.outputs.release_id }} + run: | + set -euo pipefail + + owner="${REPOSITORY%%/*}" + repo="${REPOSITORY#*/}" + asset_name="$(basename "${CHECKSUM_PATH}")" + upload_url="${API_URL}/repos/${owner}/${repo}/releases/${RELEASE_ID}/assets?name=${asset_name}" + + http_code="$(curl -sS -o asset.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"${CHECKSUM_PATH}" \ + "${upload_url}")" + + if [ "${http_code}" -lt 200 ] || [ "${http_code}" -ge 300 ]; then + echo "asset upload failed with status ${http_code}" >&2 + cat asset.json >&2 + exit 1 + fi diff --git a/.gitignore b/.gitignore index 513f519..6a6e1c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ xdebug-mcp +/build + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a7dea0c --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +BINARY_NAME := xdebug-mcp +BUILD_DIR := build +GOCACHE ?= /tmp/$(BINARY_NAME)-gocache +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) + +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) + +ifeq ($(GOOS),windows) + EXT := .exe +else + EXT := +endif + +OUTPUT := $(BUILD_DIR)/$(BINARY_NAME)-$(GOOS)-$(GOARCH)$(EXT) + +.PHONY: build test generate generate-check + +build: + @mkdir -p $(BUILD_DIR) $(GOCACHE) + GOCACHE=$(GOCACHE) GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags "-X main.version=$(VERSION)" -o $(OUTPUT) ./cmd/xdebug-mcp + +test: + @mkdir -p $(GOCACHE) + GOCACHE=$(GOCACHE) go test ./... + +generate: + @mkdir -p $(GOCACHE) + GOCACHE=$(GOCACHE) go run forge.lclr.dev/AI/mcp-framework/cmd/mcp-framework generate + +generate-check: + @mkdir -p $(GOCACHE) + GOCACHE=$(GOCACHE) go run forge.lclr.dev/AI/mcp-framework/cmd/mcp-framework generate --check