chore: semver-compliant version for dev and nightly builds (#32663)

This commit is contained in:
andig 2026-08-09 15:00:31 +02:00 • committed by GitHub
parent c1fac30171
commit 09c2ce3055
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 103 additions and 64 deletions

View file

@ -11,7 +11,7 @@ builds:
- -trimpath - -trimpath
- -tags=release - -tags=release
ldflags: ldflags:
- -X github.com/evcc-io/evcc/util.Version={{ .Tag }} -X github.com/evcc-io/evcc/util.Commit={{ .ShortCommit }} -s -w - -X github.com/evcc-io/evcc/util.Version={{ .Version }} -X github.com/evcc-io/evcc/util.Commit={{ .ShortCommit }} -s -w
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
goos: goos:
@ -56,7 +56,8 @@ checksum:
name_template: "checksums.txt" name_template: "checksums.txt"
snapshot: snapshot:
version_template: '{{ .Version }}{{ if eq (len (split .Version ".")) 2 }}.0{{ end }}+{{ .Timestamp }}' # semver pre-release of the upcoming version
version_template: "{{ incminor .Version }}-dev+{{ .ShortCommit }}"
changelog: changelog:
sort: asc sort: asc

View file

@ -63,7 +63,8 @@ checksum:
name_template: "checksums.txt" name_template: "checksums.txt"
snapshot: snapshot:
version_template: "{{ .Tag }}-next" # semver pre-release of the upcoming version
version_template: "{{ incminor .Version }}-dev+{{ .ShortCommit }}"
changelog: changelog:
sort: asc sort: asc

View file

@ -1,6 +1,13 @@
# build vars # build vars
TAG_NAME ?= $(shell test -d .git && git describe --abbrev=0 --tags) # part of the latest tag incremented for untagged builds: minor or patch
BUMP ?= minor
SHA ?= $(shell test -d .git && git rev-parse --short HEAD) SHA ?= $(shell test -d .git && git rev-parse --short HEAD)
GIT_TAG := $(shell test -d .git && git describe --abbrev=0 --tags)
# commits since the latest tag, empty or 0 means the commit is tagged
GIT_DIST := $(shell test -n "$(GIT_TAG)" && git rev-list --count $(GIT_TAG)..HEAD)
NEXT_TAG = $(shell echo $(GIT_TAG) | awk -F. -v b='$(BUMP)' '{if (b == "patch") $$NF++; else {$$2++; $$3=0}} 1' OFS=.)
# untagged builds are semver pre-releases of the upcoming version
TAG_NAME ?= $(if $(filter-out 0,$(GIT_DIST)),$(NEXT_TAG)-dev+$(SHA),$(GIT_TAG))
COMMIT := $(SHA) COMMIT := $(SHA)
# hide commit for releases # hide commit for releases
ifeq ($(RELEASE),1) ifeq ($(RELEASE),1)

View file

@ -47,7 +47,6 @@ export default {
component: AboutModal, component: AboutModal,
argTypes: { argTypes: {
installed: { control: "text" }, installed: { control: "text" },
commit: { control: "text" },
availableVersion: { control: "text" }, availableVersion: { control: "text" },
releaseNotes: { control: "text" }, releaseNotes: { control: "text" },
hasUpdater: { control: "boolean" }, hasUpdater: { control: "boolean" },
@ -95,9 +94,7 @@ StableUpdateWithUpdater.args = {
export const Nightly = Template.bind({}); export const Nightly = Template.bind({});
Nightly.args = { Nightly.args = {
installed: "0.303.1", installed: "0.304.0-dev+5ce7be4",
commit: "5ce7be4a9f3b2c1d",
availableVersion: "0.303.1",
}; };
export const DevBuild = Template.bind({}); export const DevBuild = Template.bind({});

View file

@ -36,25 +36,14 @@
<td v-if="development">---</td> <td v-if="development">---</td>
<td v-else> <td v-else>
<div class="d-flex flex-wrap column-gap-2 align-items-baseline"> <div class="d-flex flex-wrap column-gap-2 align-items-baseline">
<span class="text-nowrap"> <a
<a class="text-nowrap"
:href="releaseNotesUrl(installed)" :href="nightly ? githubCommitUrl : releaseNotesUrl(installed)"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
v{{ installed }} v{{ installed }}
</a> </a>
<template v-if="nightly">
(<a
:href="githubCommitUrl"
target="_blank"
rel="noopener noreferrer"
><span class="font-monospace">{{
shortCommit
}}</span></a
>)
</template>
</span>
<span <span
v-if="!nightly && !newVersionAvailable" v-if="!nightly && !newVersionAvailable"
class="text-muted text-nowrap" class="text-muted text-nowrap"
@ -162,7 +151,7 @@ import {
isDevelopment, isDevelopment,
isNightly, isNightly,
getReleaseName, getReleaseName,
shortCommit, commitFromVersion,
isNewVersionAvailable, isNewVersionAvailable,
} from "@/utils/version"; } from "@/utils/version";
import { defineComponent } from "vue"; import { defineComponent } from "vue";
@ -175,7 +164,6 @@ export default defineComponent({
components: { GenericModal, Logo }, components: { GenericModal, Logo },
props: { props: {
installed: { type: String, default: "" }, installed: { type: String, default: "" },
commit: String,
availableVersion: String, availableVersion: String,
releaseNotes: String, releaseNotes: String,
hasUpdater: Boolean, hasUpdater: Boolean,
@ -198,10 +186,10 @@ export default defineComponent({
return isDevelopment(this.installed); return isDevelopment(this.installed);
}, },
nightly() { nightly() {
return isNightly(this.installed, this.commit); return isNightly(this.installed);
}, },
releaseName() { releaseName() {
return getReleaseName(this.installed, this.commit); return getReleaseName(this.installed);
}, },
websiteUrl() { websiteUrl() {
return this.customWebsite || EVCC_WEBSITE; return this.customWebsite || EVCC_WEBSITE;
@ -219,11 +207,8 @@ export default defineComponent({
githubDependenciesUrl() { githubDependenciesUrl() {
return `${GITHUB_REPO}/network/dependencies`; return `${GITHUB_REPO}/network/dependencies`;
}, },
shortCommit() {
return shortCommit(this.commit);
},
githubCommitUrl() { githubCommitUrl() {
return `${GITHUB_REPO}/commit/${this.commit}`; return `${GITHUB_REPO}/commit/${commitFromVersion(this.installed)}`;
}, },
modalSize() { modalSize() {
return this.newVersionAvailable ? undefined : "sm"; return this.newVersionAvailable ? undefined : "sm";

View file

@ -36,7 +36,6 @@
:auth-disabled="authDisabled" :auth-disabled="authDisabled"
:evopt="evopt" :evopt="evopt"
:installed="installed" :installed="installed"
:commit="commit"
:available-version="availableVersion" :available-version="availableVersion"
:custom-brand="customBrand" :custom-brand="customBrand"
/> />
@ -77,7 +76,6 @@ export default defineComponent({
startupCompleted: Boolean, startupCompleted: Boolean,
evopt: { type: Object as PropType<EvOpt>, required: false }, evopt: { type: Object as PropType<EvOpt>, required: false },
installed: String, installed: String,
commit: String,
availableVersion: String, availableVersion: String,
customBrand: String, customBrand: String,
}, },

View file

@ -19,7 +19,6 @@
:auth-disabled="authDisabled" :auth-disabled="authDisabled"
:evopt="evopt" :evopt="evopt"
:installed="installed" :installed="installed"
:commit="commit"
:available-version="availableVersion" :available-version="availableVersion"
:custom-brand="customBrand" :custom-brand="customBrand"
@close="open = false" @close="open = false"
@ -51,7 +50,6 @@ export default defineComponent({
authDisabled: Boolean, authDisabled: Boolean,
evopt: { type: Object as PropType<EvOpt>, required: false }, evopt: { type: Object as PropType<EvOpt>, required: false },
installed: String, installed: String,
commit: String,
availableVersion: String, availableVersion: String,
customBrand: String, customBrand: String,
}, },

View file

@ -110,7 +110,6 @@ export default defineComponent({
authDisabled: Boolean, authDisabled: Boolean,
evopt: { type: Object as PropType<EvOpt>, required: false }, evopt: { type: Object as PropType<EvOpt>, required: false },
installed: String, installed: String,
commit: String,
availableVersion: String, availableVersion: String,
customBrand: String, customBrand: String,
}, },
@ -144,7 +143,7 @@ export default defineComponent({
return "bg-warning"; return "bg-warning";
}, },
versionLabel() { versionLabel() {
return getShortVersion(this.installed || "", this.commit); return getShortVersion(this.installed || "");
}, },
newVersionAvailable() { newVersionAvailable() {
return isNewVersionAvailable(this.installed, this.availableVersion); return isNewVersionAvailable(this.installed, this.availableVersion);

View file

@ -8,7 +8,6 @@ declare global {
app: any; app: any;
evcc?: { evcc?: {
version: string; version: string;
commit: string;
customCss: boolean; customCss: boolean;
customLogo: boolean; customLogo: boolean;
customBrand: string; customBrand: string;

View file

@ -0,0 +1,63 @@
import { describe, expect, test } from "vite-plus/test";
import {
isDevelopment,
isNightly,
commitFromVersion,
getReleaseName,
getShortVersion,
isNewVersionAvailable,
} from "./version";
const DEV = "0.0.0";
const NIGHTLY = "0.304.0-dev+abc1234";
const STABLE = "0.303.1";
describe("isDevelopment", () => {
test("only dev builds", () => {
expect(isDevelopment(DEV)).toBe(true);
expect(isDevelopment(NIGHTLY)).toBe(false);
expect(isDevelopment(STABLE)).toBe(false);
});
});
describe("isNightly", () => {
test("only versions with build metadata", () => {
expect(isNightly(DEV)).toBe(false);
expect(isNightly(NIGHTLY)).toBe(true);
expect(isNightly(STABLE)).toBe(false);
});
});
describe("commitFromVersion", () => {
test("extracts build metadata", () => {
expect(commitFromVersion(NIGHTLY)).toBe("abc1234");
expect(commitFromVersion(STABLE)).toBe("");
});
});
describe("getReleaseName", () => {
test("maps version to release name", () => {
expect(getReleaseName(DEV)).toBe("development");
expect(getReleaseName(NIGHTLY)).toBe("nightly");
expect(getReleaseName(STABLE)).toBe("stable");
});
});
describe("getShortVersion", () => {
test("formats version", () => {
expect(getShortVersion(DEV)).toBe("dev build");
expect(getShortVersion(NIGHTLY)).toBe("v0.304.0-dev+abc1234");
expect(getShortVersion(STABLE)).toBe("v0.303.1");
});
});
describe("isNewVersionAvailable", () => {
test("never for dev builds", () => {
expect(isNewVersionAvailable(DEV, "0.303.1")).toBe(false);
});
test("only when available differs", () => {
expect(isNewVersionAvailable(STABLE, "0.303.1")).toBe(false);
expect(isNewVersionAvailable("0.303.0", "0.303.1")).toBe(true);
expect(isNewVersionAvailable(STABLE, undefined)).toBe(false);
});
});

View file

@ -1,24 +1,24 @@
export function isDevelopment(version: string): boolean { export function isDevelopment(version: string): boolean {
return version === "[[.Version]]" || version === "0.0.0"; return version === "0.0.0";
} }
export function isNightly(version: string, commit?: string): boolean { // untagged builds carry the commit as build metadata: 0.304.0-dev+abc1234
return !isDevelopment(version) && !!commit; export function isNightly(version: string): boolean {
return version.includes("+");
} }
export function getReleaseName(version: string, commit?: string): string { export function commitFromVersion(version: string): string {
return version.split("+")[1] ?? "";
}
export function getReleaseName(version: string): string {
if (isDevelopment(version)) return "development"; if (isDevelopment(version)) return "development";
if (isNightly(version, commit)) return "nightly"; if (isNightly(version)) return "nightly";
return "stable"; return "stable";
} }
export function shortCommit(commit?: string): string { export function getShortVersion(version: string): string {
return commit?.substring(0, 7) || "";
}
export function getShortVersion(version: string, commit?: string): string {
if (isDevelopment(version)) return "dev build"; if (isDevelopment(version)) return "dev build";
if (isNightly(version, commit)) return `v${version} (${shortCommit(commit)})`;
return `v${version}`; return `v${version}`;
} }

View file

@ -105,7 +105,6 @@ export default defineComponent({
aboutModalProps() { aboutModalProps() {
return { return {
installed: window.evcc?.version, installed: window.evcc?.version,
commit: window.evcc?.commit,
customLogo: this.custom.logo, customLogo: this.custom.logo,
customBrand: this.custom.brand, customBrand: this.custom.brand,
customWebsite: this.custom.website, customWebsite: this.custom.website,
@ -117,7 +116,6 @@ export default defineComponent({
bottomTabBarProps() { bottomTabBarProps() {
return { return {
installed: window.evcc?.version, installed: window.evcc?.version,
commit: window.evcc?.commit,
customBrand: this.custom.brand, customBrand: this.custom.brand,
...this.collectProps(BottomTabBar, this.state), ...this.collectProps(BottomTabBar, this.state),
}; };

View file

@ -45,7 +45,6 @@ func getPreferredLanguage(header string) string {
func globalsJsHandler(custom Customization) http.HandlerFunc { func globalsJsHandler(custom Customization) http.HandlerFunc {
globals := struct { globals := struct {
Version string `json:"version"` Version string `json:"version"`
Commit string `json:"commit"`
CustomCss bool `json:"customCss"` CustomCss bool `json:"customCss"`
CustomLogo bool `json:"customLogo"` CustomLogo bool `json:"customLogo"`
Brand string `json:"customBrand"` Brand string `json:"customBrand"`
@ -54,7 +53,6 @@ func globalsJsHandler(custom Customization) http.HandlerFunc {
Phone string `json:"customPhone"` Phone string `json:"customPhone"`
}{ }{
Version: util.Version, Version: util.Version,
Commit: util.Commit,
CustomCss: custom.Css != "", CustomCss: custom.Css != "",
CustomLogo: custom.LogoLight != "", CustomLogo: custom.LogoLight != "",
Brand: custom.Brand, Brand: custom.Brand,

View file

@ -1,9 +1,6 @@
package util package util
import ( import "runtime"
"fmt"
"runtime"
)
const DevVersion = "0.0.0" const DevVersion = "0.0.0"
@ -15,10 +12,8 @@ var (
Commit = "" Commit = ""
) )
// FormattedVersion returns the version, untagged builds carry the commit as build metadata
func FormattedVersion() string { func FormattedVersion() string {
if Commit != "" {
return fmt.Sprintf("%s (%s)", Version, Commit)
}
return Version return Version
} }