updater

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 1, 2026 License: EUPL-1.2, EUPL-1.2 Imports: 12 Imported by: 0

README

Core Element Template

This repository is a template for developers to create custom HTML elements for the core web3 framework. It includes a Go backend, an Angular custom element, and a full release cycle configuration.

Getting Started

  1. Clone the repository:

    git clone https://github.com/your-username/core-element-template.git
    
  2. Install the dependencies:

    cd core-element-template
    go mod tidy
    cd ui
    npm install
    
  3. Run the development server:

    go run ./cmd/demo-cli serve
    

    This will start the Go backend and serve the Angular custom element.

Building the Custom Element

To build the Angular custom element, run the following command:

cd ui
npm run build

This will create a single JavaScript file in the dist directory that you can use in any HTML page.

Usage

To use the updater library in your Go project, you can use the UpdateService.

GitHub-based Updates
package main

import (
	"fmt"
	"log"

	"github.com/snider/updater"
)

func main() {
	config := updater.UpdateServiceConfig{
		RepoURL:        "https://github.com/owner/repo",
		Channel:        "stable",
		CheckOnStartup: updater.CheckAndUpdateOnStartup,
	}

	updateService, err := updater.NewUpdateService(config)
	if err != nil {
		log.Fatalf("Failed to create update service: %v", err)
	}

	if err := updateService.Start(); err != nil {
		fmt.Printf("Update check failed: %v\n", err)
	}
}
Generic HTTP Updates

For updates from a generic HTTP server, the server should provide a latest.json file at the root of the RepoURL. The JSON file should have the following structure:

{
  "version": "1.2.3",
  "url": "https://your-server.com/path/to/release-asset"
}

You can then configure the UpdateService as follows:

package main

import (
	"fmt"
	"log"

	"github.com/snider/updater"
)

func main() {
	config := updater.UpdateServiceConfig{
		RepoURL:        "https://your-server.com",
		CheckOnStartup: updater.CheckAndUpdateOnStartup,
	}

	updateService, err := updater.NewUpdateService(config)
	if err != nil {
		log.Fatalf("Failed to create update service: %v", err)
	}

	if err := updateService.Start(); err != nil {
		fmt.Printf("Update check failed: %v\n", err)
	}
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the EUPL-1.2 License - see the LICENSE file for details.

Documentation

Overview

Package updater provides functionality for self-updating Go applications. It supports updates from GitHub releases and generic HTTP endpoints.

Index

Examples

Constants

This section is empty.

Variables

View Source
var CheckForNewerVersion = func(owner, repo, channel string, forceSemVerPrefix bool) (*Release, bool, error) {
	client := NewGithubClient()
	ctx := context.Background()

	release, err := client.GetLatestRelease(ctx, owner, repo, channel)
	if err != nil {
		return nil, false, fmt.Errorf("error fetching latest release: %w", err)
	}

	if release == nil {
		return nil, false, nil
	}

	vCurrent := formatVersionForComparison(Version)
	vLatest := formatVersionForComparison(release.TagName)

	if semver.Compare(vCurrent, vLatest) >= 0 {
		return release, false, nil
	}

	return release, true, nil
}

CheckForNewerVersion checks if a newer version of the application is available on GitHub. It fetches the latest release for the given owner, repository, and channel, and compares its tag with the current application version.

View Source
var CheckForUpdates = func(owner, repo, channel string, forceSemVerPrefix bool, releaseURLFormat string) error {
	release, updateAvailable, err := CheckForNewerVersion(owner, repo, channel, forceSemVerPrefix)
	if err != nil {
		return err
	}

	if !updateAvailable {
		if release != nil {
			fmt.Printf("Current version %s is up-to-date with latest release %s.\n",
				formatVersionForDisplay(Version, forceSemVerPrefix),
				formatVersionForDisplay(release.TagName, forceSemVerPrefix))
		} else {
			fmt.Println("No releases found.")
		}
		return nil
	}

	fmt.Printf("Newer version %s found (current: %s). Applying update...\n",
		formatVersionForDisplay(release.TagName, forceSemVerPrefix),
		formatVersionForDisplay(Version, forceSemVerPrefix))

	downloadURL, err := GetDownloadURL(release, releaseURLFormat)
	if err != nil {
		return fmt.Errorf("error getting download URL: %w", err)
	}

	return DoUpdate(downloadURL)
}

CheckForUpdates checks for new updates on GitHub and applies them if a newer version is found. It uses the provided owner, repository, and channel to find the latest release.

View Source
var CheckForUpdatesByPullRequest = func(owner, repo string, prNumber int, releaseURLFormat string) error {
	client := NewGithubClient()
	ctx := context.Background()

	release, err := client.GetReleaseByPullRequest(ctx, owner, repo, prNumber)
	if err != nil {
		return fmt.Errorf("error fetching release for pull request: %w", err)
	}

	if release == nil {
		fmt.Printf("No release found for PR #%d.\n", prNumber)
		return nil
	}

	fmt.Printf("Release %s found for PR #%d. Applying update...\n", release.TagName, prNumber)

	downloadURL, err := GetDownloadURL(release, releaseURLFormat)
	if err != nil {
		return fmt.Errorf("error getting download URL: %w", err)
	}

	return DoUpdate(downloadURL)
}

CheckForUpdatesByPullRequest finds a release associated with a specific pull request number on GitHub and applies the update.

View Source
var CheckForUpdatesByTag = func(owner, repo string) error {
	channel := determineChannel(Version, false)
	return CheckForUpdates(owner, repo, channel, true, "")
}

CheckForUpdatesByTag checks for and applies updates from GitHub based on the channel determined by the current application's version tag (e.g., 'stable' or 'prerelease').

View Source
var CheckForUpdatesHTTP = func(baseURL string) error {
	info, err := GetLatestUpdateFromURL(baseURL)
	if err != nil {
		return err
	}

	vCurrent := formatVersionForComparison(Version)
	vLatest := formatVersionForComparison(info.Version)

	if semver.Compare(vCurrent, vLatest) >= 0 {
		fmt.Printf("Current version %s is up-to-date with latest release %s.\n", Version, info.Version)
		return nil
	}

	fmt.Printf("Newer version %s found (current: %s). Applying update...\n", info.Version, Version)
	return DoUpdate(info.URL)
}

CheckForUpdatesHTTP checks for and applies updates from a generic HTTP endpoint. The endpoint is expected to provide update information in a structured format.

View Source
var CheckOnly = func(owner, repo, channel string, forceSemVerPrefix bool, releaseURLFormat string) error {
	release, updateAvailable, err := CheckForNewerVersion(owner, repo, channel, forceSemVerPrefix)
	if err != nil {
		return err
	}

	if !updateAvailable {
		if release != nil {
			fmt.Printf("Current version %s is up-to-date with latest release %s.\n",
				formatVersionForDisplay(Version, forceSemVerPrefix),
				formatVersionForDisplay(release.TagName, forceSemVerPrefix))
		} else {
			fmt.Println("No new release found.")
		}
		return nil
	}

	fmt.Printf("New release found: %s (current version: %s)\n",
		formatVersionForDisplay(release.TagName, forceSemVerPrefix),
		formatVersionForDisplay(Version, forceSemVerPrefix))
	return nil
}

CheckOnly checks for new updates on GitHub without applying them. It prints a message indicating if a new release is available.

View Source
var CheckOnlyByTag = func(owner, repo string) error {
	channel := determineChannel(Version, false)
	return CheckOnly(owner, repo, channel, true, "")
}

CheckOnlyByTag checks for updates from GitHub based on the channel determined by the current version tag, without applying them.

View Source
var CheckOnlyHTTP = func(baseURL string) error {
	info, err := GetLatestUpdateFromURL(baseURL)
	if err != nil {
		return err
	}

	vCurrent := formatVersionForComparison(Version)
	vLatest := formatVersionForComparison(info.Version)

	if semver.Compare(vCurrent, vLatest) >= 0 {
		fmt.Printf("Current version %s is up-to-date with latest release %s.\n", Version, info.Version)
		return nil
	}

	fmt.Printf("New release found: %s (current version: %s)\n", info.Version, Version)
	return nil
}

CheckOnlyHTTP checks for updates from a generic HTTP endpoint without applying them. It prints a message if a new version is available.

View Source
var DoUpdate = func(url string) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			fmt.Printf("failed to close response body: %v\n", err)
		}
	}(resp.Body)

	err = selfupdate.Apply(resp.Body, selfupdate.Options{})
	if err != nil {
		if rerr := selfupdate.RollbackError(err); rerr != nil {
			return fmt.Errorf("failed to rollback from failed update: %v", rerr)
		}
		return fmt.Errorf("update failed: %v", err)
	}

	fmt.Println("Update applied successfully.")
	return nil
}

DoUpdate is a variable that holds the function to perform the actual update. This can be replaced in tests to prevent actual updates.

View Source
var NewAuthenticatedClient = func(ctx context.Context) *http.Client {
	token := os.Getenv("GITHUB_TOKEN")
	if token == "" {
		return http.DefaultClient
	}
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: token},
	)
	return oauth2.NewClient(ctx, ts)
}

NewAuthenticatedClient creates a new HTTP client that authenticates with the GitHub API. It uses the GITHUB_TOKEN environment variable for authentication. If the token is not set, it returns the default HTTP client.

View Source
var NewGithubClient = func() GithubClient {
	return &githubClient{}
}

NewGithubClient is a variable that holds a function to create a new GithubClient. This can be replaced in tests to inject a mock client.

Example:

updater.NewGithubClient = func() updater.GithubClient {
	return &mockClient{} // or your mock implementation
}
View Source
var Version = PkgVersion

Version holds the current version of the application. It is set at build time via ldflags or fallback to the version in package.json.

Functions

func GetDownloadURL

func GetDownloadURL(release *Release, releaseURLFormat string) (string, error)

GetDownloadURL finds the appropriate download URL for the current operating system and architecture.

It supports two modes of operation:

  1. Using a 'releaseURLFormat' template: If 'releaseURLFormat' is provided, it will be used to construct the download URL. The template can contain placeholders for the release tag '{tag}', operating system '{os}', and architecture '{arch}'.
  2. Automatic detection: If 'releaseURLFormat' is empty, the function will inspect the assets of the release to find a suitable download URL. It searches for an asset name that contains both the current OS and architecture (e.g., "my-app-linux-amd64"). If no match is found, it falls back to matching only the OS.

Example with releaseURLFormat:

release := &updater.Release{TagName: "v1.2.3"}
url, err := updater.GetDownloadURL(release, "https://example.com/downloads/{tag}/{os}/{arch}")
if err != nil {
	// handle error
}
fmt.Println(url) // "https://example.com/downloads/v1.2.3/linux/amd64" (on a Linux AMD64 system)

Example with automatic detection:

release := &updater.Release{
	Assets: []updater.ReleaseAsset{
		{Name: "my-app-linux-amd64", DownloadURL: "https://example.com/download/linux-amd64"},
		{Name: "my-app-windows-amd64", DownloadURL: "https://example.com/download/windows-amd64"},
	},
}
url, err := updater.GetDownloadURL(release, "")
if err != nil {
	// handle error
}
fmt.Println(url) // "https://example.com/download/linux-amd64" (on a Linux AMD64 system)

func ParseRepoURL

func ParseRepoURL(repoURL string) (owner string, repo string, err error)

ParseRepoURL extracts the owner and repository name from a GitHub URL. It handles standard GitHub URL formats.

Example
package main

import (
	"fmt"
	"log"

	"github.com/host-uk/core/pkg/updater"
)

func main() {
	owner, repo, err := updater.ParseRepoURL("https://github.com/owner/repo")
	if err != nil {
		log.Fatalf("Failed to parse repo URL: %v", err)
	}
	fmt.Printf("Owner: %s, Repo: %s", owner, repo)
}
Output:
Owner: owner, Repo: repo

Types

type GenericUpdateInfo

type GenericUpdateInfo struct {
	Version string `json:"version"` // The version number of the update.
	URL     string `json:"url"`     // The URL to download the update from.
}

GenericUpdateInfo holds the information from a latest.json file. This file is expected to be at the root of a generic HTTP update server.

func GetLatestUpdateFromURL

func GetLatestUpdateFromURL(baseURL string) (*GenericUpdateInfo, error)

GetLatestUpdateFromURL fetches and parses a latest.json file from a base URL. The server at the baseURL should host a 'latest.json' file that contains the version and download URL for the latest update.

Example of latest.json:

{
  "version": "1.2.3",
  "url": "https://your-server.com/path/to/release-asset"
}

type GithubClient

type GithubClient interface {
	// GetPublicRepos fetches the public repositories for a user or organization.
	GetPublicRepos(ctx context.Context, userOrOrg string) ([]string, error)
	// GetLatestRelease fetches the latest release for a given repository and channel.
	GetLatestRelease(ctx context.Context, owner, repo, channel string) (*Release, error)
	// GetReleaseByPullRequest fetches a release associated with a specific pull request number.
	GetReleaseByPullRequest(ctx context.Context, owner, repo string, prNumber int) (*Release, error)
}

GithubClient defines the interface for interacting with the GitHub API. This allows for mocking the client in tests.

type Release

type Release struct {
	TagName    string         `json:"tag_name"`   // The name of the tag for the release.
	PreRelease bool           `json:"prerelease"` // Indicates if the release is a pre-release.
	Assets     []ReleaseAsset `json:"assets"`     // A list of assets associated with the release.
}

Release represents a GitHub release.

type ReleaseAsset

type ReleaseAsset struct {
	Name        string `json:"name"`                 // The name of the asset.
	DownloadURL string `json:"browser_download_url"` // The URL to download the asset.
}

ReleaseAsset represents a single asset from a GitHub release.

type Repo

type Repo struct {
	CloneURL string `json:"clone_url"` // The URL to clone the repository.
}

Repo represents a repository from the GitHub API.

type StartupCheckMode

type StartupCheckMode int

StartupCheckMode defines the updater's behavior on startup.

const (
	// NoCheck disables any checks on startup.
	NoCheck StartupCheckMode = iota
	// CheckOnStartup checks for updates on startup but does not apply them.
	CheckOnStartup
	// CheckAndUpdateOnStartup checks for and applies updates on startup.
	CheckAndUpdateOnStartup
)

type UpdateService

type UpdateService struct {
	// contains filtered or unexported fields
}

UpdateService provides a configurable interface for handling application updates. It can be configured to check for updates on startup and, if desired, apply them automatically. The service can handle updates from both GitHub releases and generic HTTP servers.

func NewUpdateService

func NewUpdateService(config UpdateServiceConfig) (*UpdateService, error)

NewUpdateService creates and configures a new UpdateService. It parses the repository URL to determine if it's a GitHub repository and extracts the owner and repo name.

Example
package main

import (
	"fmt"
	"log"

	"github.com/host-uk/core/pkg/updater"
)

func main() {
	// Mock the update check functions to prevent actual updates during tests
	updater.CheckForUpdates = func(owner, repo, channel string, forceSemVerPrefix bool, releaseURLFormat string) error {
		fmt.Println("CheckForUpdates called")
		return nil
	}
	defer func() {
		updater.CheckForUpdates = nil // Restore original function
	}()

	config := updater.UpdateServiceConfig{
		RepoURL:        "https://github.com/owner/repo",
		Channel:        "stable",
		CheckOnStartup: updater.CheckAndUpdateOnStartup,
	}
	updateService, err := updater.NewUpdateService(config)
	if err != nil {
		log.Fatalf("Failed to create update service: %v", err)
	}
	if err := updateService.Start(); err != nil {
		log.Printf("Update check failed: %v", err)
	}
}
Output:
CheckForUpdates called

func (*UpdateService) Start

func (s *UpdateService) Start() error

Start initiates the update check based on the service configuration. It determines whether to perform a GitHub or HTTP-based update check based on the RepoURL. The behavior of the check is controlled by the CheckOnStartup setting in the configuration.

type UpdateServiceConfig

type UpdateServiceConfig struct {
	// RepoURL is the URL to the repository for updates. It can be a GitHub
	// repository URL (e.g., "https://github.com/owner/repo") or a base URL
	// for a generic HTTP update server.
	RepoURL string
	// Channel specifies the release channel to track (e.g., "stable", "prerelease").
	// This is only used for GitHub-based updates.
	Channel string
	// CheckOnStartup determines the update behavior when the service starts.
	CheckOnStartup StartupCheckMode
	// ForceSemVerPrefix toggles whether to enforce a 'v' prefix on version tags for display.
	// If true, a 'v' prefix is added if missing. If false, it's removed if present.
	ForceSemVerPrefix bool
	// ReleaseURLFormat provides a template for constructing the download URL for a
	// release asset. The placeholder {tag} will be replaced with the release tag.
	ReleaseURLFormat string
}

UpdateServiceConfig holds the configuration for the UpdateService.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL