Compare commits

...

16 Commits

Author SHA1 Message Date
lukaszraczylo 18b9b474e0 fix: remove unnecessary verified parameter from homebrew cask
The verified parameter is only needed when the URL domain differs
from the homepage domain. Since both point to github.com, Homebrew
audit flags this as unnecessary.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 02:06:43 +00:00
lukaszraczylo f5a118fd1a Use shared PR workflow. 2025-12-08 02:06:43 +00:00
lukaszraczylo a1c4133e94 Update go.mod and go.sum (#57) 2025-12-08 01:22:53 +00:00
lukaszraczylo 2a51752663 Trigger autoupdate. 2025-12-08 01:14:40 +00:00
lukaszraczylo 0bc848f6f4 fixup! fixup! Update binary confusion. 2025-12-07 16:29:21 +00:00
lukaszraczylo 8590963822 fixup! Update binary confusion. 2025-12-07 16:27:57 +00:00
lukaszraczylo f4285403f7 Update binary confusion. 2025-12-07 16:27:04 +00:00
lukaszraczylo b3d104f0a8 fixup! fixup! fixup! fixup! fixup! fixup! Move updater to REST api. 2025-12-07 16:23:19 +00:00
lukaszraczylo 7a70ed6614 fixup! fixup! fixup! fixup! fixup! Move updater to REST api. 2025-12-07 16:09:36 +00:00
lukaszraczylo f9a18995d0 fixup! fixup! fixup! fixup! Move updater to REST api. 2025-12-07 15:59:21 +00:00
lukaszraczylo 66756b6772 fixup! fixup! fixup! Move updater to REST api. 2025-12-07 15:42:15 +00:00
lukaszraczylo a0f1ab6930 fixup! fixup! Move updater to REST api. 2025-12-07 15:36:03 +00:00
lukaszraczylo b80929ff52 fixup! Move updater to REST api. 2025-12-07 15:31:03 +00:00
lukaszraczylo be189187ba Move updater to REST api. 2025-12-07 15:28:37 +00:00
lukaszraczylo 336d8cc163 Move to shared github actions and goreleaser 2025-12-07 15:11:16 +00:00
lukaszraczylo b8fdc4bfb5 Update go.mod and go.sum
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-07 03:29:23 +00:00
15 changed files with 1352 additions and 500 deletions
+11 -65
View File
@@ -5,70 +5,16 @@ on:
schedule:
- cron: "0 3 * * *"
env:
GO_VERSION: ">=1.21"
permissions:
contents: write
actions: write
pull-requests: write
jobs:
# This job is responsible for preparation of the build
# environment variables.
prepare:
name: Preparing build context
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
id: cache
with:
go-version: ${{env.GO_VERSION}}
cache-dependency-path: "**/*.sum"
- name: Go get dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: |
go get ./...
# This job is responsible for running tests and linting the codebase
test:
name: "Unit testing"
runs-on: ubuntu-latest
container: golang:1
needs: [prepare]
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensure full history is checked out
token: ${{ secrets.GHCR_TOKEN }}
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{env.GO_VERSION}}
cache-dependency-path: "**/*.sum"
- name: Install dependencies
run: |
apt-get update
apt-get install ca-certificates make -y
update-ca-certificates
go mod tidy
go get -u -v ./...
go mod tidy -v
- name: Run unit tests
run: |
export GITHUB_TOKEN=${{ secrets.GHCR_TOKEN }}
CI_RUN=${CI} make test
git config --global --add safe.directory /__w/semver-generator/semver-generator
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Update go.mod and go.sum"
commit_options: "--no-verify --signoff"
file_pattern: "go.mod go.sum"
autoupdate:
uses: lukaszraczylo/shared-actions/.github/workflows/go-autoupdate.yaml@main
with:
go-version: "1.24"
release-workflow: "release.yaml"
secrets:
pat-token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
+16
View File
@@ -0,0 +1,16 @@
name: Pull Request
on:
pull_request:
branches:
- main
push:
branches:
- "**"
- "!main"
jobs:
pr-checks:
uses: lukaszraczylo/shared-actions/.github/workflows/go-pr.yaml@main
with:
go-version: "1.24"
+16 -213
View File
@@ -1,222 +1,25 @@
name: Test, scan, build, release
name: Test, build, release
on:
workflow_dispatch:
push:
paths-ignore:
- '**.md'
- '**/release.yaml'
- 'action.yml'
- '**.md'
- '**/release.yaml'
- 'action.yml'
branches:
- "master"
- "main"
- main
env:
ENABLE_CODE_LINT: false
ENABLE_CODE_SCANS: false
DEPLOY: false
GO_VERSION: 1.21
permissions:
contents: write
packages: write
jobs:
prepare:
name: Preparing build context
runs-on: ubuntu-latest
outputs:
SANITISED_REPOSITORY_NAME: ${{ steps.get_env.outputs.SANITISED_REPOSITORY_NAME }}
DOCKER_IMAGE: ${{ steps.get_env.outputs.DOCKER_IMAGE }}
GITHUB_COMMIT_NUMBER: ${{ steps.get_env.outputs.GITHUB_COMMIT_NUMBER }}
GITHUB_SHA: ${{ steps.get_env.outputs.GITHUB_SHA }}
GITHUB_RUN_ID: ${{ steps.get_env.outputs.GITHUB_RUN_ID }}
RELEASE_VERSION: ${{ steps.get_env.outputs.RELEASE_VERSION }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
fetch-depth: '0'
- name: Setting environment variables
id: get_env
run: |
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/lukaszraczylo/semver-generator/releases/latest | grep -E ".*browser_download_url.*linux-" | grep -vE "(arm64|md5)" \
| cut -d '"' -f 4)
curl -s -L -o semver-gen "$DOWNLOAD_URL" && chmod +x semver-gen
TMP_SANITISED_REPOSITORY_NAME=$(echo ${{ github.event.repository.name }} | sed -e 's|\.|-|g')
TMP_GITHUB_COMMITS_COUNT=$(git rev-list --count HEAD)
TMP_GITHUB_COUNT_NUMBER=$(echo ${GITHUB_RUN_NUMBER})
TMP_RELEASE_VERSION=$(./semver-gen generate -l -c config-release.yaml | sed -e 's|SEMVER ||g')
echo ">> Release version: $TMP_RELEASE_VERSION <<"
# Setting outputs
echo "SANITISED_REPOSITORY_NAME=$TMP_SANITISED_REPOSITORY_NAME" > $GITHUB_OUTPUT
echo "DOCKER_IMAGE=ghcr.io/${{ github.repository_owner }}/$TMP_SANITISED_REPOSITORY_NAME" >> $GITHUB_OUTPUT
echo "GITHUB_COMMIT_NUMBER=$TMP_GITHUB_COMMITS_COUNT" >> $GITHUB_OUTPUT
echo "GITHUB_SHA=$(echo ${GITHUB_SHA::8})" >> $GITHUB_OUTPUT
echo "GITHUB_RUN_ID=$TMP_GITHUB_COUNT_NUMBER" >> $GITHUB_OUTPUT
echo "RELEASE_VERSION=$TMP_RELEASE_VERSION" >> $GITHUB_OUTPUT
test:
needs: [ prepare ]
name: Code checks pipeline
runs-on: ubuntu-latest
container: github/super-linter:v3.15.5
env:
CI: true
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Lint Code Base
if: env.ENABLE_CODE_LINT == true
env:
VALIDATE_ALL_CODEBASE: true
VALIDATE_DOCKERFILE: false # this leaves us with hadolint only
VALIDATE_GO: false # disable bulk validation of go files, run the linter manually
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GHCR_TOKEN }}
LOG_LEVEL: WARN
run: |
golangci-lint run --exclude-use-default ./...
/action/lib/linter.sh
- name: Run unit tests
env:
GITHUB_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
make test CI_RUN=${CI}
- name: Upload codecov result
uses: codecov/codecov-action@v3
continue-on-error: true
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: coverage.out
fail_ci_if_error: false
code_scans:
needs: [ prepare ]
name: Code scans pipeline
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Configure git for private modules
run: |
make update
- name: WriteGoList
run: go list -json -m all > go.list
- name: Running nancy
if: env.ENABLE_CODE_SCANS == true
uses: sonatype-nexus-community/nancy-github-action@main
- name: Running gosec
if: env.ENABLE_CODE_SCANS == true
uses: securego/gosec@master
with:
args: ./...
build-binary:
needs: [ prepare, test, code_scans ]
name: Binary compilation and release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Build binaries
run: |
LOCAL_VERSION=${{ needs.prepare.outputs.RELEASE_VERSION }} make dist-release
- name: Get list of the commits since last release
run: |
echo "$(git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"%h %s")" > .release_notes
- name: Create release [semver]
uses: ncipollo/release-action@v1
with:
bodyFile: ./.release_notes
name: version ${{ needs.prepare.outputs.RELEASE_VERSION }}
token: ${{ secrets.GHCR_TOKEN }}
tag: ${{ needs.prepare.outputs.RELEASE_VERSION }}
prerelease: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main' }}
artifacts: "dist/*"
allowUpdates: true
- name: Delete existing v1 tag and release
run: |
gh release delete v1 --cleanup-tag -y
env:
GH_TOKEN: ${{ secrets.GHCR_TOKEN }}
- name: Create release [v1]
uses: ncipollo/release-action@v1
with:
bodyFile: ./.release_notes
name: v1 - ${{ needs.prepare.outputs.RELEASE_VERSION }}
token: ${{ secrets.GHCR_TOKEN }}
tag: v1
prerelease: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/main' }}
artifacts: "dist/*"
allowUpdates: true
makeLatest: false
build-docker:
needs: [ prepare, test, code_scans, build-binary ]
name: Docker image build
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.ACTOR }}
password: ${{ secrets.GHCR_TOKEN }}
- name: Prepare for push
id: prep
run: |
if [ -z "${{ needs.prepare.outputs.RELEASE_VERSION }}" ]; then
TAGS="${{ needs.prepare.outputs.DOCKER_IMAGE }}:${{ needs.prepare.outputs.GITHUB_SHA }},${{ needs.prepare.outputs.DOCKER_IMAGE }}:latest,${{ needs.prepare.outputs.DOCKER_IMAGE }}:v1"
else
TAGS="${{ needs.prepare.outputs.DOCKER_IMAGE }}:${{ needs.prepare.outputs.GITHUB_SHA }},${{ needs.prepare.outputs.DOCKER_IMAGE }}:${{ needs.prepare.outputs.RELEASE_VERSION }},${{ needs.prepare.outputs.DOCKER_IMAGE }}:latest,${{ needs.prepare.outputs.DOCKER_IMAGE }}:v1"
fi
echo "TAGS=$TAGS" >> $GITHUB_OUTPUT
BRANCH=$(echo ${GITHUB_REF##*/} | tr '[A-Z]' '[a-z]')
LABELS="org.opencontainers.image.revision=${{ needs.prepare.outputs.GITHUB_SHA }}"
LABELS="$LABELS,org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
LABELS="$LABELS,org.opencontainers.image.version=$VERSION"
LABELS="$LABELS,com.github.repo.branch=$BRANCH"
LABELS="$LABELS,com.github.repo.dockerfile=Dockerfile"
echo "LABELS=$LABELS" >> $GITHUB_OUTPUT
BUILD_ARGS="BRANCH=$BRANCH"
echo "args=$BUILD_ARGS" >> $GITHUB_OUTPUT
- name: Build image
id: docker_build
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
platforms: linux/arm64,linux/amd64
push: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
tags: ${{ steps.prep.outputs.tags }}
build-args: |
GITHUB_AUTH_TOKEN=${{ secrets.GHCR_TOKEN }}
MICROSERVICE_NAME=${{ github.event.repository.name }}
GITHUB_COMMIT_NUMBER=${{ needs.prepare.outputs.GITHUB_COMMIT_NUMBER }}
GITHUB_SHA=${{ needs.prepare.outputs.GITHUB_SHA }}
${{ steps.prep.outputs.args }}
labels: ${{ steps.prep.outputs.labels }}
no-cache: false
release:
uses: lukaszraczylo/shared-actions/.github/workflows/go-release.yaml@main
with:
go-version: "1.24"
docker-enabled: true
rolling-release-tag: "v1"
semver-config: "config-release.yaml"
secrets: inherit
+45
View File
@@ -0,0 +1,45 @@
# Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
paths:
- 'docs/**'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: 'docs/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+89
View File
@@ -0,0 +1,89 @@
version: 2
before:
hooks:
- go mod tidy
builds:
- id: semver-gen
main: .
binary: semver-generator
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.PKG_VERSION={{.Version}}
archives:
- id: semver-gen
formats: [tar.gz]
name_template: "semver-generator-{{ .Os }}-{{ .Arch }}"
format_overrides:
- goos: windows
formats: [zip]
files:
- LICENSE
- README.md
- config.yaml
checksum:
name_template: "semver-generator-checksums.txt"
algorithm: sha256
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^Merge'
- '^WIP'
- '^Update go.mod'
release:
github:
owner: lukaszraczylo
name: semver-generator
name_template: "version {{.Version}}"
draft: false
prerelease: auto
dockers_v2:
- images:
- "ghcr.io/lukaszraczylo/semver-generator"
tags:
- "{{ .Version }}"
- "latest"
- "v1"
platforms:
- linux/amd64
- linux/arm64
dockerfile: Dockerfile.goreleaser
extra_files:
- config-release.yaml
- entrypoint.sh
homebrew_casks:
- name: semver-generator
repository:
owner: lukaszraczylo
name: homebrew-taps
token: "{{ .Env.HOMEBREW_TAP_TOKEN }}"
directory: Casks
homepage: https://github.com/lukaszraczylo/semver-generator
description: "Automatic semantic version generator based on git commit messages"
license: MIT
hooks:
post:
install: |
if OS.mac?
system_command "/usr/bin/xattr",
args: ["-dr", "com.apple.quarantine", "#{staged_path}/semver-generator"]
end
+7
View File
@@ -0,0 +1,7 @@
FROM ubuntu:jammy
ARG TARGETPLATFORM
COPY ${TARGETPLATFORM}/semver-generator /go/src/app/semver-generator
COPY config-release.yaml /go/src/app/config.yaml
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
+25 -7
View File
@@ -49,15 +49,23 @@ export GITHUB_TOKEN=yourPersonalApiToken
#### As a binary
##### Homebrew (macOS)
```bash
brew install --cask lukaszraczylo/taps/semver-generator
```
##### Manual Download
You can download latest versions of the binaries from the [release page](https://github.com/lukaszraczylo/semver-generator/releases/latest).
**Supported OS and architectures:**
Darwin ARM64/AMD64, Linux ARM64/AMD64, Windows AMD64
```bash
bash$ ./semver-gen generate -r https://github.com/nextapps-de/winbox
bash$ semver-generator generate -r https://github.com/nextapps-de/winbox
SEMVER 9.0.10
bash$ ./semver-gen generate -l
bash$ semver-generator generate -l
SEMVER 5.1.1
```
@@ -65,8 +73,8 @@ SEMVER 5.1.1
```yaml
Usage:
semver-gen generate [flags]
semver-gen [command]
semver-generator generate [flags]
semver-generator [command]
Available Commands:
generate Generates semantic version
@@ -76,15 +84,25 @@ Flags:
-c, --config string Path to config file (default "semver.yaml")
-d, --debug Enable debug mode
-e, --existing Respect existing tags
-h, --help help for semver-gen
-h, --help help for semver-generator
-l, --local Use local repository
-r, --repository string Remote repository URL. (default "https://github.com/lukaszraczylo/simple-gql-client")
-b, --branch string Remote repository URL Branch. (default "main")
-s, --strict Strict matching
-u, --update Update binary with latest
-u, --update Update binary with latest (no authentication required)
-v, --version Display version
```
##### Self-Update
The binary can update itself to the latest version:
```bash
semver-generator -u
```
This downloads the latest release for your platform directly from GitHub releases. No authentication is required.
#### As a github action
```yaml
@@ -199,7 +217,7 @@ wording:
* `blacklist`: terms to ignore when processing commits. Any commit containing these terms will be skipped in version calculations. Useful for ignoring merge commits, feature branch names, and other unwanted triggers.
* `wording`: words the program should look for in the git commits to increment (patch|minor|major)
### Good to know
### Good to knows
* Word matching uses fuzzy search AND is case INSENSITIVE
* I do not recommend using common words ( like "the" from the example configuration )
+12 -13
View File
@@ -26,27 +26,26 @@ import (
)
var (
err error
repo *Setup
PKG_VERSION string
)
// Setup represents the application setup
type Setup struct {
RepositoryName string
RepositoryBranch string
LocalConfigFile string
Generate bool
UseLocal bool
GitRepo utils.GitRepository
Config *utils.Config
Semver utils.SemVer
RepositoryName string
RepositoryBranch string
LocalConfigFile string
Generate bool
UseLocal bool
GitRepo utils.GitRepository
Config *utils.Config
Semver utils.SemVer
}
// Initialize the fuzzy search function in the utils package
func init() {
utils.InitLogger(false) // Will be updated in main based on debug flag
// Set the fuzzy search function
utils.FuzzyFind = fuzzy.FindNormalizedFold
}
@@ -72,12 +71,12 @@ func main() {
if PKG_VERSION != latestRelease && latestReleaseOk {
outdatedMsg = fmt.Sprintf("(Latest available: %s)", latestRelease)
}
utils.Info("semver-gen", map[string]interface{}{
"version": PKG_VERSION,
"version": PKG_VERSION,
"outdated": outdatedMsg,
})
if outdatedMsg != "" {
utils.Info("semver-gen", map[string]interface{}{
"message": "You can update automatically with: semver-gen -u",
+395 -120
View File
@@ -1,148 +1,423 @@
package utils
import (
"flag"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"github.com/lukaszraczylo/ask"
graphql "github.com/lukaszraczylo/go-simple-graphql"
"github.com/melbahja/got"
"strings"
"time"
)
// UpdatePackage updates the binary with the latest version
func UpdatePackage() bool {
ghToken, ghTokenSet := os.LookupEnv("GITHUB_TOKEN")
if !ghTokenSet {
Error("GITHUB_TOKEN not set", nil)
return false
}
const (
// GitHub API endpoint for latest release
githubReleasesURL = "https://api.github.com/repos/lukaszraczylo/semver-generator/releases/latest"
// Request timeout for HTTP requests
requestTimeout = 10 * time.Second
)
binaryName := fmt.Sprintf("semver-gen-%s-%s", runtime.GOOS, runtime.GOARCH)
Info("Checking for updates", map[string]interface{}{"binaryName": binaryName})
gql := graphql.NewConnection()
gql.SetEndpoint("https://api.github.com/graphql")
gql.SetOutput("mapstring")
// ReleaseInfo contains information about a GitHub release
type ReleaseInfo struct {
TagName string `json:"tag_name"`
HTMLURL string `json:"html_url"`
Name string `json:"name"`
Assets []ReleaseAsset `json:"assets"`
}
headers := map[string]interface{}{
"Authorization": fmt.Sprintf("Bearer %s", ghToken),
}
variables := map[string]interface{}{
"binaryName": binaryName,
}
var query = `query ($binaryName: String) {
repository(name: "semver-generator", owner: "lukaszraczylo") {
latestRelease {
releaseAssets(first: 10, name: $binaryName) {
edges {
node {
name
downloadUrl
}
}
}
}
}
}`
result, err := gql.Query(query, variables, headers)
// ReleaseAsset contains information about a release asset
type ReleaseAsset struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
}
// UpdateInfo contains information about an available update
type UpdateInfo struct {
CurrentVersion string
LatestVersion string
ReleaseURL string
DownloadURL string
}
// httpClient is the HTTP client used for requests (allows mocking in tests)
var httpClient = &http.Client{
Timeout: requestTimeout,
}
// CheckLatestRelease checks for the latest release version using REST API
// Returns the latest version tag and true if successful, empty string and false otherwise
func CheckLatestRelease() (string, bool) {
release, err := fetchLatestRelease(context.Background())
if err != nil {
Error("Unable to query GitHub API", map[string]interface{}{"error": err.Error()})
Debug("Unable to check latest release", map[string]interface{}{"error": err.Error()})
return "", false
}
version := normalizeVersion(release.TagName)
return version, true
}
// CheckForUpdate checks if a newer version is available
// Returns UpdateInfo if an update is available, nil otherwise
func CheckForUpdate(currentVersion string) *UpdateInfo {
release, err := fetchLatestRelease(context.Background())
if err != nil {
return nil
}
latestVersion := normalizeVersion(release.TagName)
current := normalizeVersion(currentVersion)
if isNewerVersion(latestVersion, current) {
downloadURL := findBinaryAsset(release.Assets)
return &UpdateInfo{
CurrentVersion: current,
LatestVersion: latestVersion,
ReleaseURL: release.HTMLURL,
DownloadURL: downloadURL,
}
}
return nil
}
// UpdatePackage downloads and installs the latest version
func UpdatePackage() bool {
Info("Checking for updates", nil)
release, err := fetchLatestRelease(context.Background())
if err != nil {
Error("Unable to fetch latest release", map[string]interface{}{"error": err.Error()})
return false
}
output, ok := ask.For(result, "repository.latestRelease.releaseAssets.edges[0].node.downloadUrl").String("")
if !ok {
Error("Unable to obtain download url for the binary", map[string]interface{}{
"binary": binaryName,
"output": output,
downloadURL := findBinaryAsset(release.Assets)
if downloadURL == "" {
Error("Unable to find binary for current platform", map[string]interface{}{
"os": runtime.GOOS,
"arch": runtime.GOARCH,
})
return false
}
// Skip actual download in test mode
if flag.Lookup("test.v") == nil && os.Getenv("CI") == "" {
downloadedBinaryPath := fmt.Sprintf("/tmp/%s", binaryName)
g := got.New()
err = g.Download(output, downloadedBinaryPath)
if err != nil {
Error("Unable to download binary", map[string]interface{}{
"error": err.Error(),
"binaryPath": downloadedBinaryPath,
})
return false
}
currentBinary, err := os.Executable()
if err != nil {
Error("Unable to obtain current binary path", map[string]interface{}{
"error": err.Error(),
})
return false
}
err = os.Rename(downloadedBinaryPath, currentBinary)
if err != nil {
Error("Unable to overwrite current binary", map[string]interface{}{
"error": err.Error(),
})
return false
}
err = os.Chmod(currentBinary, 0777)
if err != nil {
Error("Unable to make binary executable", map[string]interface{}{
"error": err.Error(),
})
return false
}
Info("Downloading update", map[string]interface{}{
"version": release.TagName,
"url": downloadURL,
})
// Download to temp file
tempFile, err := downloadBinary(downloadURL)
if err != nil {
Error("Unable to download binary", map[string]interface{}{"error": err.Error()})
return false
}
defer os.Remove(tempFile) // Clean up temp file on failure
// Get current binary path
currentBinary, err := os.Executable()
if err != nil {
Error("Unable to get current binary path", map[string]interface{}{"error": err.Error()})
return false
}
// Replace current binary
if err := replaceBinary(tempFile, currentBinary); err != nil {
Error("Unable to replace binary", map[string]interface{}{"error": err.Error()})
return false
}
Info("Update successful", map[string]interface{}{
"version": release.TagName,
})
return true
}
// CheckLatestRelease checks for the latest release version
func CheckLatestRelease() (string, bool) {
ghToken, ghTokenSet := os.LookupEnv("GITHUB_TOKEN")
if !ghTokenSet {
return "[no GITHUB_TOKEN set]", false
// fetchLatestRelease fetches the latest release info from GitHub REST API
func fetchLatestRelease(ctx context.Context) (*ReleaseInfo, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, githubReleasesURL, nil)
if err != nil {
return nil, err
}
gql := graphql.NewConnection()
gql.SetEndpoint("https://api.github.com/graphql")
headers := map[string]interface{}{
"Authorization": fmt.Sprintf("bearer %s", ghToken),
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("User-Agent", "semver-generator")
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
variables := map[string]interface{}{}
var query = `query {
repository(name: "semver-generator", owner: "lukaszraczylo", followRenames: true) {
releases(last: 2) {
nodes {
tag {
name
}
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("GitHub API returned status %d", resp.StatusCode)
}
var release ReleaseInfo
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return nil, err
}
return &release, nil
}
// findBinaryAsset finds the download URL for the current platform
func findBinaryAsset(assets []ReleaseAsset) string {
// Build expected binary name pattern
// Format: semver-gen-{version}-{os}-{arch}.tar.gz or just semver-gen-{os}-{arch}
osName := runtime.GOOS
archName := runtime.GOARCH
for _, asset := range assets {
name := strings.ToLower(asset.Name)
// Match patterns like "semver-gen-1.0.0-darwin-arm64.tar.gz" or "semver-gen-darwin-arm64"
if strings.Contains(name, osName) && strings.Contains(name, archName) {
// Prefer tar.gz archives
if strings.HasSuffix(name, ".tar.gz") {
return asset.BrowserDownloadURL
}
}
}`
result, err := gql.Query(query, variables, headers)
}
// Fallback: try to find any matching binary without tar.gz
for _, asset := range assets {
name := strings.ToLower(asset.Name)
if strings.Contains(name, osName) && strings.Contains(name, archName) {
// Skip checksums
if strings.Contains(name, "checksum") || strings.HasSuffix(name, ".sha256") || strings.HasSuffix(name, ".md5") {
continue
}
return asset.BrowserDownloadURL
}
}
return ""
}
// downloadBinary downloads the binary to a temp file and returns the path
func downloadBinary(url string) (string, error) {
resp, err := httpClient.Get(url)
if err != nil {
Error("Unable to query GitHub API", map[string]interface{}{"error": err.Error()})
return "", false
return "", err
}
output, _ := ask.For(result, "repository.releases.nodes[0].tag.name").String("")
if output == "v1" {
output, _ = ask.For(result, "repository.releases.nodes[1].tag.name").String("")
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("download failed with status %d", resp.StatusCode)
}
return output, true
}
// Create temp file
tempFile, err := os.CreateTemp("", "semver-generator-update-*")
if err != nil {
return "", err
}
tempPath := tempFile.Name()
// Check if it's a tar.gz archive
if strings.HasSuffix(url, ".tar.gz") {
// For tar.gz, we need to extract the binary
if err := extractTarGz(resp.Body, tempFile); err != nil {
tempFile.Close()
os.Remove(tempPath)
return "", err
}
} else {
// Direct binary download
if _, err := io.Copy(tempFile, resp.Body); err != nil {
tempFile.Close()
os.Remove(tempPath)
return "", err
}
}
tempFile.Close()
return tempPath, nil
}
// extractTarGz extracts the semver-generator binary from a tar.gz archive
func extractTarGz(r io.Reader, destFile *os.File) error {
// For simplicity, we'll download the whole archive to a temp file first,
// then use tar command to extract. This avoids adding archive/tar dependency.
// Create temp archive file
archiveFile, err := os.CreateTemp("", "semver-generator-archive-*.tar.gz")
if err != nil {
return err
}
archivePath := archiveFile.Name()
defer os.Remove(archivePath)
if _, err := io.Copy(archiveFile, r); err != nil {
archiveFile.Close()
return err
}
archiveFile.Close()
// Extract using tar command
extractDir, err := os.MkdirTemp("", "semver-generator-extract-*")
if err != nil {
return err
}
defer os.RemoveAll(extractDir)
// Use tar to extract
cmd := fmt.Sprintf("tar -xzf %s -C %s", archivePath, extractDir)
if err := runCommand(cmd); err != nil {
return fmt.Errorf("failed to extract archive: %w", err)
}
// Find the binary in the extracted files
// Support both new name (semver-generator) and old name (semver-gen) for backwards compatibility
binaryPath := ""
entries, err := os.ReadDir(extractDir)
if err != nil {
return err
}
// First try to find semver-generator (new name)
for _, entry := range entries {
if entry.Name() == "semver-generator" {
binaryPath = fmt.Sprintf("%s/%s", extractDir, entry.Name())
break
}
}
// Fallback to semver-gen (old name) for older releases
if binaryPath == "" {
for _, entry := range entries {
if entry.Name() == "semver-gen" {
binaryPath = fmt.Sprintf("%s/%s", extractDir, entry.Name())
break
}
}
}
if binaryPath == "" {
return fmt.Errorf("binary not found in archive (looked for semver-generator and semver-gen)")
}
// Copy the binary to the destination
srcFile, err := os.Open(binaryPath)
if err != nil {
return err
}
defer srcFile.Close()
// Seek to beginning of dest file and truncate
if _, err := destFile.Seek(0, 0); err != nil {
return err
}
if err := destFile.Truncate(0); err != nil {
return err
}
if _, err := io.Copy(destFile, srcFile); err != nil {
return err
}
return nil
}
// runCommand runs a shell command
func runCommand(cmdStr string) error {
return runCommandFunc(cmdStr)
}
// runCommandFunc is the function used to run commands (allows mocking in tests)
var runCommandFunc = func(cmdStr string) error {
cmd := exec.Command("sh", "-c", cmdStr)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// replaceBinary replaces the current binary with the new one
func replaceBinary(newBinary, currentBinary string) error {
// Make the new binary executable
if err := os.Chmod(newBinary, 0755); err != nil {
return err
}
// Rename (atomic on most systems)
if err := os.Rename(newBinary, currentBinary); err != nil {
// If rename fails (e.g., cross-device), try copy
return copyFile(newBinary, currentBinary)
}
return nil
}
// copyFile copies a file from src to dst
func copyFile(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return err
}
// Make executable
return os.Chmod(dst, 0755)
}
// normalizeVersion removes 'v' or 'V' prefix and trims whitespace
func normalizeVersion(v string) string {
v = strings.TrimSpace(v)
v = strings.TrimPrefix(v, "v")
v = strings.TrimPrefix(v, "V")
return v
}
// isNewerVersion compares two semver-like versions
// Returns true if latest is newer than current
func isNewerVersion(latest, current string) bool {
latestParts := parseVersionParts(latest)
currentParts := parseVersionParts(current)
for i := 0; i < len(latestParts) && i < len(currentParts); i++ {
if latestParts[i] > currentParts[i] {
return true
}
if latestParts[i] < currentParts[i] {
return false
}
}
return len(latestParts) > len(currentParts)
}
// parseVersionParts splits a version string into numeric parts
func parseVersionParts(v string) []int {
// Remove any suffix like -beta, -rc1, etc.
if idx := strings.IndexAny(v, "-+"); idx != -1 {
v = v[:idx]
}
parts := strings.Split(v, ".")
result := make([]int, 0, len(parts))
for _, p := range parts {
var num int
fmt.Sscanf(p, "%d", &num)
result = append(result, num)
}
return result
}
// FormatUpdateMessage formats a user-friendly update notification
func (u *UpdateInfo) FormatUpdateMessage() string {
return fmt.Sprintf("New version available: %s (current: %s) - %s",
u.LatestVersion, u.CurrentVersion, u.ReleaseURL)
}
+227 -53
View File
@@ -1,66 +1,240 @@
package utils
import (
"flag"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCheckLatestRelease(t *testing.T) {
// Initialize logger
InitLogger(true)
// Save original environment variables
originalToken := os.Getenv("GITHUB_TOKEN")
defer os.Setenv("GITHUB_TOKEN", originalToken)
// Test with no token
os.Unsetenv("GITHUB_TOKEN")
release, ok := CheckLatestRelease()
assert.Equal(t, "[no GITHUB_TOKEN set]", release, "Should return no token message")
assert.False(t, ok, "Should return false when no token is set")
// Test with token but simulating API error
// Set a dummy token that won't work with the GitHub API
os.Setenv("GITHUB_TOKEN", "dummy-token")
release, ok = CheckLatestRelease()
assert.Equal(t, "", release, "Should return empty string on API error")
assert.False(t, ok, "Should return false on API error")
// We can't reliably test the successful API call in unit tests
// as it would require a valid GitHub token and network access
}
func TestUpdatePackage(t *testing.T) {
// Initialize logger
InitLogger(true)
// Save original environment variables
originalToken := os.Getenv("GITHUB_TOKEN")
defer os.Setenv("GITHUB_TOKEN", originalToken)
// Test with no token
os.Unsetenv("GITHUB_TOKEN")
result := UpdatePackage()
assert.False(t, result, "Should return false when no token is set")
// Test with token but simulating API error
os.Setenv("GITHUB_TOKEN", "dummy-token")
result = UpdatePackage()
assert.False(t, result, "Should return false on API error")
// Create a test flag to simulate test mode
if flag.Lookup("test.v") == nil {
// This is a hack to simulate the test flag being set
// which is used in the UpdatePackage function to skip actual download
flag.Bool("test.v", true, "")
func TestNormalizeVersion(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"v1.0.0", "1.0.0"},
{"1.0.0", "1.0.0"},
{" v2.1.3 ", "2.1.3"},
{"V1.0.0", "1.0.0"},
{"v", ""},
{"", ""},
}
// We can't fully test the update functionality as it would modify the binary
// but we've tested the token check logic and API error handling
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := normalizeVersion(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
// Note: We're not using mock transports for these tests to avoid
// adding complexity. The tests focus on the token presence logic and error handling.
func TestParseVersionParts(t *testing.T) {
tests := []struct {
input string
expected []int
}{
{"1.0.0", []int{1, 0, 0}},
{"2.1.3", []int{2, 1, 3}},
{"1.0", []int{1, 0}},
{"10.20.30", []int{10, 20, 30}},
{"1.0.0-beta", []int{1, 0, 0}},
{"1.0.0-rc1", []int{1, 0, 0}},
{"1.0.0+build123", []int{1, 0, 0}},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := parseVersionParts(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsNewerVersion(t *testing.T) {
tests := []struct {
name string
latest string
current string
expected bool
}{
{"major version bump", "2.0.0", "1.0.0", true},
{"minor version bump", "1.1.0", "1.0.0", true},
{"patch version bump", "1.0.1", "1.0.0", true},
{"same version", "1.0.0", "1.0.0", false},
{"current is newer major", "1.0.0", "2.0.0", false},
{"current is newer minor", "1.0.0", "1.1.0", false},
{"current is newer patch", "1.0.0", "1.0.1", false},
{"multi-digit versions", "1.10.0", "1.9.0", true},
{"longer version is newer", "1.0.1", "1.0", true},
{"shorter version is older", "1.0", "1.0.1", false},
{"complex comparison", "2.1.3", "2.1.2", true},
{"real world example", "0.2.0", "0.1.0", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isNewerVersion(tt.latest, tt.current)
assert.Equal(t, tt.expected, result)
})
}
}
func TestFindBinaryAsset(t *testing.T) {
assets := []ReleaseAsset{
{Name: "semver-gen-1.0.0-linux-amd64.tar.gz", BrowserDownloadURL: "https://example.com/linux-amd64.tar.gz"},
{Name: "semver-gen-1.0.0-darwin-arm64.tar.gz", BrowserDownloadURL: "https://example.com/darwin-arm64.tar.gz"},
{Name: "semver-gen-1.0.0-darwin-amd64.tar.gz", BrowserDownloadURL: "https://example.com/darwin-amd64.tar.gz"},
{Name: "semver-gen-1.0.0-windows-amd64.zip", BrowserDownloadURL: "https://example.com/windows-amd64.zip"},
{Name: "semver-gen-1.0.0-checksums.txt", BrowserDownloadURL: "https://example.com/checksums.txt"},
}
// Test finding the correct asset for the current platform
url := findBinaryAsset(assets)
assert.NotEmpty(t, url, "Should find a binary for the current platform")
assert.NotContains(t, url, "checksum", "Should not return checksum file")
}
func TestFindBinaryAssetEmpty(t *testing.T) {
assets := []ReleaseAsset{}
url := findBinaryAsset(assets)
assert.Empty(t, url, "Should return empty string when no assets")
}
func TestUpdateInfo_FormatUpdateMessage(t *testing.T) {
info := &UpdateInfo{
CurrentVersion: "1.0.0",
LatestVersion: "2.0.0",
ReleaseURL: "https://github.com/lukaszraczylo/semver-generator/releases/tag/v2.0.0",
}
msg := info.FormatUpdateMessage()
assert.Contains(t, msg, "2.0.0")
assert.Contains(t, msg, "1.0.0")
assert.Contains(t, msg, "https://github.com/lukaszraczylo/semver-generator/releases/tag/v2.0.0")
}
func TestCheckLatestRelease(t *testing.T) {
// Initialize logger
InitLogger(false)
// Create a mock server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{
"tag_name": "v1.2.3",
"html_url": "https://github.com/lukaszraczylo/semver-generator/releases/tag/v1.2.3",
"name": "Release 1.2.3",
"assets": []
}`))
}))
defer server.Close()
// Note: In a real test, we'd need to mock the HTTP client or the URL
// For now, we just test the network error case
release, ok := CheckLatestRelease()
// This will either succeed (if network is available) or fail gracefully
if ok {
assert.NotEmpty(t, release)
}
}
func TestCheckForUpdate(t *testing.T) {
InitLogger(false)
// Test with a very old version - should show update available if network works
info := CheckForUpdate("0.0.1")
// This will either return update info or nil depending on network
if info != nil {
assert.NotEmpty(t, info.LatestVersion)
assert.Equal(t, "0.0.1", info.CurrentVersion)
}
// Test with a very new version - should not show update
info = CheckForUpdate("999.999.999")
assert.Nil(t, info, "Should not show update for future version")
}
func TestFetchLatestReleaseError(t *testing.T) {
InitLogger(false)
// Save original client
originalClient := httpClient
defer func() { httpClient = originalClient }()
// Create a mock server that returns an error
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
// We can't easily test this without modifying the URL constant
// but we can test the error handling by checking that it fails gracefully
release, ok := CheckLatestRelease()
// The result depends on whether the real GitHub API is accessible
_ = release
_ = ok
}
func TestCopyFile(t *testing.T) {
// Create a temp source file
srcContent := []byte("test content")
srcFile, err := os.CreateTemp("", "test-*")
assert.NoError(t, err)
defer os.Remove(srcFile.Name())
_, err = srcFile.Write(srcContent)
assert.NoError(t, err)
srcFile.Close()
// Create destination path
dstPath := srcFile.Name() + ".copy"
defer os.Remove(dstPath)
// Copy the file
err = copyFile(srcFile.Name(), dstPath)
assert.NoError(t, err)
// Verify the content
content, err := os.ReadFile(dstPath)
assert.NoError(t, err)
assert.Equal(t, srcContent, content)
}
func TestReplaceBinary(t *testing.T) {
// Create a temp "new" binary
newContent := []byte("new binary content")
newFile, err := os.CreateTemp("", "new-binary-*")
assert.NoError(t, err)
defer os.Remove(newFile.Name())
_, err = newFile.Write(newContent)
assert.NoError(t, err)
newFile.Close()
// Create a temp "current" binary
currentFile, err := os.CreateTemp("", "current-binary-*")
assert.NoError(t, err)
currentPath := currentFile.Name()
defer os.Remove(currentPath)
currentFile.Close()
// Replace the binary
err = replaceBinary(newFile.Name(), currentPath)
assert.NoError(t, err)
// Verify the content was replaced
content, err := os.ReadFile(currentPath)
assert.NoError(t, err)
assert.Equal(t, newContent, content)
}
func TestUpdatePackageNoBinary(t *testing.T) {
InitLogger(false)
// This test verifies UpdatePackage handles the case where no binary is found
// by testing with a mock that returns empty assets
// Note: This would need proper mocking of httpClient to test fully
}
+1
View File
@@ -0,0 +1 @@
semver-generator.raczylo.com
+500
View File
@@ -0,0 +1,500 @@
<!doctype html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>semver-generator - Automatic Semantic Version Generator</title>
<meta
name="description"
content="Automatic semantic version generator based on git commit messages. Use as CLI, GitHub Action, or Docker container."
/>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: 'class'
}
</script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
/>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
<style>
body { font-family: "Inter", sans-serif; }
code, pre { font-family: "JetBrains Mono", monospace; }
.theme-transition {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.animate-fade-in-up { animation: fadeInUp 0.6s ease-out; }
.animate-float { animation: float 3s ease-in-out infinite; }
.glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark .glass {
background: rgba(17, 24, 39, 0.7);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.gradient-text {
background: linear-gradient(135deg, #10b981 0%, #3b82f6 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.dark .gradient-text {
background: linear-gradient(135deg, #34d399 0%, #60a5fa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.shadow-modern { box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.1); }
.dark .shadow-modern { box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.4); }
html { scroll-behavior: smooth; }
</style>
<script>
if (localStorage.theme === "dark" || (!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>
</head>
<body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 theme-transition">
<!-- Navigation -->
<nav class="fixed w-full glass shadow-modern z-50 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="flex justify-between h-16 items-center">
<a href="#" class="flex items-center hover:opacity-80 transition-opacity duration-300 gap-2">
<i class="fas fa-code-branch text-2xl gradient-text"></i>
<span class="text-xl font-bold gradient-text">semver-generator</span>
</a>
<div class="hidden md:flex space-x-6">
<a href="#features" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Features</a>
<a href="#installation" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Installation</a>
<a href="#usage" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Usage</a>
<a href="#configuration" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 font-medium">Configuration</a>
</div>
<div class="flex items-center space-x-4">
<button id="theme-toggle" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 p-2 min-w-[44px] min-h-[44px] flex items-center justify-center" aria-label="Toggle theme">
<i class="fas fa-moon dark:hidden text-xl"></i>
<i class="fas fa-sun hidden dark:inline text-xl"></i>
</button>
<a href="https://github.com/lukaszraczylo/semver-generator" target="_blank" class="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 p-2 min-w-[44px] min-h-[44px] flex items-center justify-center" aria-label="View on GitHub">
<i class="fab fa-github text-xl"></i>
</a>
<button id="mobile-menu-toggle" class="md:hidden text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 p-2 min-w-[44px] min-h-[44px] flex items-center justify-center" aria-label="Toggle menu">
<i class="fas fa-bars text-xl" id="menu-open-icon"></i>
<i class="fas fa-times text-xl hidden" id="menu-close-icon"></i>
</button>
</div>
</div>
</div>
<div id="mobile-menu" class="hidden md:hidden border-t border-gray-200 dark:border-gray-700">
<div class="px-4 py-3 space-y-1 bg-white dark:bg-gray-800">
<a href="#features" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Features</a>
<a href="#installation" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Installation</a>
<a href="#usage" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Usage</a>
<a href="#configuration" class="block px-3 py-3 text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-700 rounded font-medium">Configuration</a>
</div>
</div>
</nav>
<!-- Hero Section -->
<section class="relative pt-24 sm:pt-32 pb-12 sm:pb-20 overflow-hidden">
<div class="absolute inset-0 bg-gradient-to-br from-emerald-50 via-cyan-50 to-blue-50 dark:from-gray-900 dark:via-emerald-900/20 dark:to-blue-900/20 theme-transition"></div>
<div class="absolute top-0 -left-4 w-72 h-72 bg-emerald-300 dark:bg-emerald-500 rounded-full mix-blend-multiply dark:mix-blend-soft-light filter blur-xl opacity-20 animate-float"></div>
<div class="absolute top-0 -right-4 w-72 h-72 bg-cyan-300 dark:bg-cyan-500 rounded-full mix-blend-multiply dark:mix-blend-soft-light filter blur-xl opacity-20 animate-float" style="animation-delay: 1s;"></div>
<div class="absolute -bottom-8 left-20 w-72 h-72 bg-blue-300 dark:bg-blue-500 rounded-full mix-blend-multiply dark:mix-blend-soft-light filter blur-xl opacity-20 animate-float" style="animation-delay: 2s;"></div>
<div class="relative max-w-6xl mx-auto px-4 sm:px-6">
<div class="text-center">
<div class="mb-8 sm:mb-10 flex justify-center animate-fade-in-up">
<div class="text-8xl sm:text-9xl animate-float">
<i class="fas fa-code-branch gradient-text"></i>
</div>
</div>
<h1 class="text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-gray-100 mb-4 sm:mb-6 leading-tight animate-fade-in-up" style="animation-delay: 0.1s;">
Semantic Version<br /><span class="gradient-text">Generator</span>
</h1>
<p class="text-base sm:text-lg md:text-xl text-gray-600 dark:text-gray-300 mb-8 sm:mb-10 max-w-2xl mx-auto leading-relaxed px-4 animate-fade-in-up" style="animation-delay: 0.2s;">
Automatic semantic versioning based on git commit messages. Use as a CLI tool, GitHub Action, or Docker container.
</p>
<div class="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center mb-8 sm:mb-12 px-4 animate-fade-in-up" style="animation-delay: 0.3s;">
<a href="#installation" class="group relative bg-gradient-to-r from-emerald-500 to-blue-600 hover:from-emerald-600 hover:to-blue-700 text-white px-8 py-3 rounded-lg font-medium transition-all duration-300 min-h-[48px] flex items-center justify-center shadow-lg hover:shadow-xl hover:scale-105">
<span class="relative z-10">Get Started</span>
</a>
<a href="https://github.com/lukaszraczylo/semver-generator" class="group glass hover:shadow-lg text-gray-900 dark:text-gray-100 px-8 py-3 rounded-lg font-medium transition-all duration-300 min-h-[48px] flex items-center justify-center hover:scale-105">
<i class="fab fa-github mr-2"></i>View on GitHub
</a>
</div>
<div class="flex flex-wrap justify-center gap-2 sm:gap-4 text-sm px-4">
<img src="https://img.shields.io/github/v/release/lukaszraczylo/semver-generator" alt="Version" class="h-5" />
<img src="https://img.shields.io/github/license/lukaszraczylo/semver-generator" alt="License" class="h-5" />
<img src="https://goreportcard.com/badge/github.com/lukaszraczylo/semver-generator" alt="Go Report" class="h-5" />
</div>
<div class="mt-12 sm:mt-16 max-w-3xl mx-auto px-4 animate-fade-in-up" style="animation-delay: 0.4s;">
<div class="relative group">
<div class="absolute -inset-1 bg-gradient-to-r from-emerald-500 to-blue-600 rounded-xl blur opacity-25 group-hover:opacity-50 transition duration-500"></div>
<div class="relative bg-gray-900 rounded-xl p-6 text-left">
<div class="flex items-center gap-2 mb-4">
<div class="w-3 h-3 rounded-full bg-red-500"></div>
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<span class="ml-2 text-gray-400 text-sm">terminal</span>
</div>
<pre class="text-gray-100 text-sm sm:text-base overflow-x-auto"><code><span class="text-gray-400">$</span> semver-generator generate -l
<span class="text-emerald-400">SEMVER</span> 1.5.2
<span class="text-gray-400">$</span> semver-generator generate -r https://github.com/user/repo
<span class="text-emerald-400">SEMVER</span> 2.3.0</code></pre>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="py-12 sm:py-16 md:py-20 bg-white dark:bg-gray-900 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="text-center mb-8 sm:mb-12">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Features</h2>
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Automatic versioning that just works</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<i class="fas fa-magic text-white"></i>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Automatic Versioning</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Calculates version based on commit message keywords</p>
</div>
</div>
</div>
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<i class="fab fa-github text-white"></i>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">GitHub Action</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Use directly in your CI/CD workflows</p>
</div>
</div>
</div>
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-cyan-500 to-cyan-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<i class="fab fa-docker text-white"></i>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Docker Ready</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Multi-arch Docker images for any environment</p>
</div>
</div>
</div>
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-purple-500 to-purple-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<i class="fas fa-cog text-white"></i>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Configurable Keywords</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Define your own trigger words for version bumps</p>
</div>
</div>
</div>
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-orange-500 to-orange-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<i class="fas fa-tag text-white"></i>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Respects Tags</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Optional mode to respect existing git tags</p>
</div>
</div>
</div>
<div class="glass p-5 rounded-xl group hover:shadow-lg transition-all duration-300">
<div class="flex items-start gap-4">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-pink-500 to-pink-600 flex items-center justify-center flex-shrink-0 group-hover:scale-110 transition-transform duration-300">
<i class="fas fa-flask text-white"></i>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-1">Release Candidates</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Support for RC versions like 1.3.37-rc.1</p>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Installation Section -->
<section id="installation" class="py-12 sm:py-16 md:py-20 bg-gray-50 dark:bg-gray-800 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="text-center mb-8 sm:mb-12">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Installation</h2>
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Get started in seconds</p>
</div>
<div class="max-w-3xl mx-auto space-y-6">
<div class="glass p-6 rounded-xl">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center">
<i class="fas fa-beer mr-2 text-amber-500"></i>
Homebrew (macOS)
</h3>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto"><code>brew install --cask lukaszraczylo/taps/semver-generator</code></pre>
</div>
<div class="glass p-6 rounded-xl">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center">
<i class="fas fa-download mr-2 text-emerald-500"></i>
Manual Download
</h3>
<p class="text-gray-600 dark:text-gray-400 mb-3">Download from the <a href="https://github.com/lukaszraczylo/semver-generator/releases/latest" class="text-emerald-600 dark:text-emerald-400 hover:underline">releases page</a>.</p>
<p class="text-sm text-gray-500 dark:text-gray-400">Supported: Darwin ARM64/AMD64, Linux ARM64/AMD64, Windows AMD64</p>
</div>
<div class="glass p-6 rounded-xl">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-3 flex items-center">
<i class="fab fa-docker mr-2 text-blue-500"></i>
Docker
</h3>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto"><code>docker pull ghcr.io/lukaszraczylo/semver-generator:latest</code></pre>
</div>
</div>
</div>
</section>
<!-- Usage Section -->
<section id="usage" class="py-12 sm:py-16 md:py-20 bg-white dark:bg-gray-900 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="text-center mb-8 sm:mb-12">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Usage</h2>
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Multiple ways to use semver-generator</p>
</div>
<div class="max-w-4xl mx-auto space-y-8">
<div class="glass p-6 rounded-xl">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
<i class="fas fa-terminal mr-2 text-emerald-500"></i>
CLI Usage
</h3>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto mb-4"><code><span class="text-gray-400"># Local repository</span>
semver-generator generate -l
<span class="text-gray-400"># Remote repository</span>
semver-generator generate -r https://github.com/user/repo
<span class="text-gray-400"># With custom config</span>
semver-generator generate -l -c semver.yaml
<span class="text-gray-400"># Strict mode (only exact matches)</span>
semver-generator generate -l -s
<span class="text-gray-400"># Respect existing tags</span>
semver-generator generate -l -e
<span class="text-gray-400"># Self-update to latest version (no auth required)</span>
semver-generator -u</code></pre>
<div class="grid sm:grid-cols-2 gap-4 text-sm">
<div>
<h4 class="font-medium text-gray-900 dark:text-gray-100 mb-2">Flags</h4>
<ul class="space-y-1 text-gray-600 dark:text-gray-400">
<li><code class="text-emerald-600 dark:text-emerald-400">-l, --local</code> Use local repository</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-r, --repository</code> Remote repository URL</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-c, --config</code> Path to config file</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-s, --strict</code> Strict matching</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-e, --existing</code> Respect existing tags</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-d, --debug</code> Enable debug mode</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-u, --update</code> Self-update to latest version</li>
<li><code class="text-emerald-600 dark:text-emerald-400">-v, --version</code> Display current version</li>
</ul>
</div>
</div>
</div>
<div class="glass p-6 rounded-xl">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4 flex items-center text-xl">
<i class="fab fa-github mr-2 text-blue-500"></i>
GitHub Action
</h3>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm"><code>jobs:
version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.semver.outputs.semantic_version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: '0'
- name: Generate version
id: semver
uses: lukaszraczylo/semver-generator@v1
with:
config_file: semver.yaml
repository_local: true
- name: Use version
run: echo "Version: ${{ steps.semver.outputs.semantic_version }}"</code></pre>
</div>
</div>
</div>
</section>
<!-- How It Works Section -->
<section class="py-12 sm:py-16 md:py-20 bg-gray-50 dark:bg-gray-800 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="text-center mb-8 sm:mb-12">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">How It Works</h2>
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Version calculation based on commit messages</p>
</div>
<div class="max-w-3xl mx-auto">
<div class="glass p-6 rounded-xl">
<pre class="text-gray-700 dark:text-gray-300 text-sm overflow-x-auto"><code>0.0.1 - PATCH - starting commit
0.0.2 - PATCH - another commit
0.0.4 - PATCH - commit with 'Update' => DOUBLE increment PATCH
0.1.0 - MINOR - commit with 'Change' => increment MINOR, reset PATCH
0.1.1 - PATCH - additional commit
1.0.1 - MAJOR - commit with 'BREAKING' => INCREMENT MAJOR, reset MINOR
1.0.2 - PATCH - another commit</code></pre>
</div>
<div class="mt-6 grid sm:grid-cols-3 gap-4 text-center">
<div class="glass p-4 rounded-xl">
<div class="text-3xl font-bold text-red-500 mb-2">MAJOR</div>
<p class="text-sm text-gray-600 dark:text-gray-400">Breaking changes</p>
<p class="text-xs text-gray-500 dark:text-gray-500 mt-1">Keywords: "breaking"</p>
</div>
<div class="glass p-4 rounded-xl">
<div class="text-3xl font-bold text-yellow-500 mb-2">MINOR</div>
<p class="text-sm text-gray-600 dark:text-gray-400">New features</p>
<p class="text-xs text-gray-500 dark:text-gray-500 mt-1">Keywords: "change", "improve"</p>
</div>
<div class="glass p-4 rounded-xl">
<div class="text-3xl font-bold text-emerald-500 mb-2">PATCH</div>
<p class="text-sm text-gray-600 dark:text-gray-400">Bug fixes</p>
<p class="text-xs text-gray-500 dark:text-gray-500 mt-1">Keywords: "update", "initial"</p>
</div>
</div>
</div>
</div>
</section>
<!-- Configuration Section -->
<section id="configuration" class="py-12 sm:py-16 md:py-20 bg-white dark:bg-gray-900 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="text-center mb-8 sm:mb-12">
<h2 class="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-3 sm:mb-4">Configuration</h2>
<p class="text-base sm:text-lg text-gray-600 dark:text-gray-300 px-4">Customize keywords and behavior</p>
</div>
<div class="max-w-3xl mx-auto">
<div class="glass p-6 rounded-xl">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-4">semver.yaml</h3>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm"><code><span class="text-gray-400">version:</span> 1
<span class="text-gray-400"># Starting version (optional)</span>
<span class="text-emerald-400">force:</span>
<span class="text-blue-400">major:</span> 1
<span class="text-blue-400">minor:</span> 0
<span class="text-blue-400">patch:</span> 0
<span class="text-blue-400">commit:</span> 69fbe2df696f40281b9104ff073d26186cde1024
<span class="text-gray-400"># Commits to ignore</span>
<span class="text-emerald-400">blacklist:</span>
- "Merge branch"
- "Merge pull request"
- "feature/"
<span class="text-gray-400"># Keywords for version bumps</span>
<span class="text-emerald-400">wording:</span>
<span class="text-blue-400">patch:</span>
- update
- initial
- fix
<span class="text-blue-400">minor:</span>
- change
- improve
- add
<span class="text-blue-400">major:</span>
- breaking
<span class="text-blue-400">release:</span>
- release-candidate
- add-rc</code></pre>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="py-8 bg-gray-100 dark:bg-gray-800 theme-transition">
<div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="flex items-center gap-2">
<i class="fas fa-code-branch text-xl gradient-text"></i>
<span class="font-semibold gradient-text">semver-generator</span>
</div>
<div class="flex items-center gap-6">
<a href="https://github.com/lukaszraczylo/semver-generator" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100">
<i class="fab fa-github text-xl"></i>
</a>
<a href="https://github.com/lukaszraczylo/semver-generator/issues" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 text-sm">
Issues
</a>
<a href="https://github.com/lukaszraczylo/semver-generator/releases" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 text-sm">
Releases
</a>
</div>
<p class="text-gray-500 dark:text-gray-400 text-sm">MIT License</p>
</div>
</div>
</footer>
<script>
// Theme toggle
document.getElementById('theme-toggle').addEventListener('click', function() {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.theme = 'light';
} else {
document.documentElement.classList.add('dark');
localStorage.theme = 'dark';
}
});
// Mobile menu toggle
document.getElementById('mobile-menu-toggle').addEventListener('click', function() {
const menu = document.getElementById('mobile-menu');
const openIcon = document.getElementById('menu-open-icon');
const closeIcon = document.getElementById('menu-close-icon');
menu.classList.toggle('hidden');
openIcon.classList.toggle('hidden');
closeIcon.classList.toggle('hidden');
});
// Close mobile menu when clicking a link
document.querySelectorAll('#mobile-menu a').forEach(link => {
link.addEventListener('click', () => {
document.getElementById('mobile-menu').classList.add('hidden');
document.getElementById('menu-open-icon').classList.remove('hidden');
document.getElementById('menu-close-icon').classList.add('hidden');
});
});
</script>
</body>
</html>
+2 -2
View File
@@ -60,11 +60,11 @@ if [[ ! -z "$INPUT_DEBUGMODE" ]]; then
echo "----"
echo "FLAGS: $FLAGS"
echo "----"
/go/src/app/semver-gen generate $FLAGS $*
/go/src/app/semver-generator generate $FLAGS $*
echo "----"
fi
OUT_SEMVER_GEN=$(/go/src/app/semver-gen generate $FLAGS $*)
OUT_SEMVER_GEN=$(/go/src/app/semver-generator generate $FLAGS $*)
[ $? -eq 0 ] || exit 1
CLEAN_SEMVER=$(echo $OUT_SEMVER_GEN | sed -e 's|SEMVER ||g')
echo "semantic_version=$CLEAN_SEMVER" >> $GITHUB_OUTPUT
+2 -9
View File
@@ -7,11 +7,8 @@ toolchain go1.24.6
require (
github.com/go-git/go-git/v5 v5.16.4
github.com/lithammer/fuzzysearch v1.1.8
github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9
github.com/lukaszraczylo/go-simple-graphql v1.2.89
github.com/lukaszraczylo/graphql-monitoring-proxy v0.41.20
github.com/lukaszraczylo/graphql-monitoring-proxy v0.42.4
github.com/lukaszraczylo/pandati v0.0.29
github.com/melbahja/got v0.7.0
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
@@ -21,18 +18,16 @@ require (
dario.cat/mergo v1.0.2 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/avast/retry-go/v4 v4.7.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/gookit/goutil v0.7.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
@@ -60,9 +55,7 @@ require (
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/net v0.47.0 // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
golang.org/x/text v0.31.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+4 -18
View File
@@ -9,8 +9,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/avast/retry-go/v4 v4.7.0 h1:yjDs35SlGvKwRNSykujfjdMxMhMQQM0TnIjJaHB+Zio=
github.com/avast/retry-go/v4 v4.7.0/go.mod h1:ZMPDa3sY2bKgpLtap9JRUgk2yTAba7cgiFhqxY2Sg6Q=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
@@ -35,8 +33,8 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
@@ -45,15 +43,11 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gookit/goutil v0.7.2 h1:NSiqWWY+BT0MwIlKDeSVPfQmr9xTkkAqwDjhplobdgo=
github.com/gookit/goutil v0.7.2/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -71,12 +65,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9 h1:pL8B9mjv6RPUfKYYGm/uJ7QL6Ndf+z+OEl0qJE6KmEc=
github.com/lukaszraczylo/ask v0.0.0-20240916204100-6e9ef53a62d9/go.mod h1:M+UVdyqZs++xtEPrascaVmZdOMhCnxjZ2SgH+xHpR0c=
github.com/lukaszraczylo/go-simple-graphql v1.2.89 h1:Xbu1Ny+a0lT2Sr2SaSC8mcHmGQDwGD4TJKk4DDd+PwA=
github.com/lukaszraczylo/go-simple-graphql v1.2.89/go.mod h1:PxQYblQDZISmYYj8sNfazAWxAOh1rhAtU208y+uPV8s=
github.com/lukaszraczylo/graphql-monitoring-proxy v0.41.20 h1:554N+HD5cTY074Y0LrL82cYQNCG1qDV3QKULgdLovs0=
github.com/lukaszraczylo/graphql-monitoring-proxy v0.41.20/go.mod h1:1FLcH7q+7cjUgQxyeVeF7ouBamGpcJZgqDF+j+cuFxI=
github.com/lukaszraczylo/graphql-monitoring-proxy v0.42.4 h1:gq/bEq+JzPes8WR24HHys9VvVWREDEXOWFoVSOFXCCw=
github.com/lukaszraczylo/graphql-monitoring-proxy v0.42.4/go.mod h1:1FLcH7q+7cjUgQxyeVeF7ouBamGpcJZgqDF+j+cuFxI=
github.com/lukaszraczylo/pandati v0.0.29 h1:WUEWm1+hWjE5KJbIL8OctG00x2dk4XKGJSlrjhxZ55k=
github.com/lukaszraczylo/pandati v0.0.29/go.mod h1:+DyTWKFaXd+jIfe7GW5w2S5PyTko/RXxMyOa+Vl713A=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -86,8 +76,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/melbahja/got v0.7.0 h1:YHbiuNZVS8fIkyV0iXyThQQliwlKZb5h4k80zBVovxg=
github.com/melbahja/got v0.7.0/go.mod h1:27cUstWCEfj6HBESMTGzCFY24Qj+QNMWot3+KuxguQU=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -166,8 +154,6 @@ golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=