diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml deleted file mode 100644 index b01847c3f..000000000 --- a/.forgejo/workflows/build.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: Build - -# Forgejo bevorzugt .forgejo/workflows gegenüber .github/workflows. Die 23 -# Upstream-Workflows laufen hier also nicht mit — die verlangen ohnehin -# GitHub-spezifische Runner-Label (depot-ubuntu-24.04-arm) und Secrets. - -on: - push: - branches: [master] - workflow_dispatch: - -jobs: - verify: - name: Go build & test - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - # main.go bindet dist/ per //go:embed ein. Das Verzeichnis wird vom - # Frontend-Build erzeugt und ist gitignored — für den reinen Compile-Check - # reicht ein Platzhalter, go:embed braucht nur mindestens eine Datei. - - name: Stub embedded assets - run: mkdir -p dist && echo stub > dist/index.html - - - name: Build - run: go build ./... - - - name: Vet - run: go vet ./util/sponsor/... ./charger/... ./core/... ./server/... - - - name: Test - run: CGO_ENABLED=0 go test ./... - - binary: - name: evcc binary - runs-on: ubuntu-latest - needs: verify - - steps: - # fetch-depth 0: das Makefile leitet VERSION aus git describe --tags ab - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - - uses: actions/setup-node@v4 - with: - node-version: 26 - cache: npm - - - name: Install UI deps - run: npm ci - - - name: Build UI - run: make ui - - - name: Build binary - run: make build - - - uses: actions/upload-artifact@v3 - with: - name: evcc - path: evcc diff --git a/.github/actions/build-toolchain/action.yml b/.github/actions/build-toolchain/action.yml index 89adc042f..7d0f17453 100644 --- a/.github/actions/build-toolchain/action.yml +++ b/.github/actions/build-toolchain/action.yml @@ -13,7 +13,7 @@ inputs: runs: using: composite steps: - - uses: actions/setup-go@v7 + - uses: actions/setup-go@v6 with: go-version-file: go.mod cache: false # managed explicitly below to keep the scoped namespaces diff --git a/.github/actions/docker-cache/action.yml b/.github/actions/docker-cache/action.yml index e543a5469..250fecf31 100644 --- a/.github/actions/docker-cache/action.yml +++ b/.github/actions/docker-cache/action.yml @@ -32,12 +32,6 @@ runs: ${{ runner.os }}-docker-mounts-${{ inputs.cache-scope }}- ${{ runner.os }}-docker-mounts-main- - # the go module cache restores read-only, which fails the dance's cleanup - # of the source directory after injection - - name: Make cache mounts writable - shell: bash - run: test -d .docker-cache && chmod -R u+w .docker-cache || true - - name: Inject cache mounts uses: reproducible-containers/buildkit-cache-dance@5422eac04292c961a382e0f584ea0f03ad9da723 # v3.4.0 with: diff --git a/.github/scripts/release-tag.sh b/.github/scripts/release-tag.sh index 4219ae8c4..fd3bf867d 100755 --- a/.github/scripts/release-tag.sh +++ b/.github/scripts/release-tag.sh @@ -3,9 +3,7 @@ # # Feature releases (patch level 0) must be tagged on master. Bugfix releases may # be tagged on any branch so an older release line can be serviced without -# shipping everything that landed on master since. Either way the tag must build -# on the previous release of its own line, so a tag pushed from the wrong branch -# cannot ship an older commit under a newer version. +# shipping everything that landed on master since. # # Prints `latest=` for consumption as a GitHub step output. Only the # newest release may move the `latest` pointers (docker tag, homebrew formula, @@ -29,43 +27,13 @@ validate() { return 1 fi - local line="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" - if [[ ${BASH_REMATCH[3]} == 0 ]] && ! git merge-base --is-ancestor "$tag" "$MASTER_REF"; then echo "::error::feature release '$tag' must be tagged on master" >&2 return 1 fi - local releases - releases=$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort --version-sort) || true - - # a release must build on its own line. a tag pushed from the wrong branch - # would otherwise ship an older commit under a newer version. - local earlier - # awk must not exit early here, that would SIGPIPE the grep it reads from - earlier=$(printf '%s\n' "$releases" | grep -E "^${line//./\\.}\." | awk -v t="$tag" '$0 == t { seen = 1 } !seen') - - if [[ -n $earlier ]]; then - # the newest predecessor the tag really builds on. a tag from the wrong - # branch has none, a repeated one has it at the very same commit. - local prev - prev=$(while read -r rel; do - if git merge-base --is-ancestor "$rel" "$tag"; then echo "$rel"; fi - done <<<"$earlier" | tail -1) - - if [[ -z $prev ]]; then - echo "::error::release '$tag' does not build on $(tail -1 <<<"$earlier"), it was tagged on the wrong branch" >&2 - return 1 - fi - - if [[ $(git rev-parse "$tag^{commit}") == $(git rev-parse "$prev^{commit}") ]]; then - echo "::error::release '$tag' points at the same commit as '$prev'" >&2 - return 1 - fi - fi - local newest - newest=$(printf '%s\n' "$releases" | tail -1) + newest=$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort --version-sort | tail -1) || true if [[ $newest == "$tag" ]]; then echo "latest=true" @@ -87,12 +55,10 @@ self_test() { git checkout --quiet -b fix commit two git tag 0.1.1 # bugfix release off master - git tag 0.1.2 # same commit as its predecessor git tag 0.2.0 # feature release off master git checkout --quiet master commit three git tag 0.3.0 - git tag 0.2.1 # tagged on the wrong branch, does not contain 0.2.0 git tag 9.9.9-fork # tags from forks must not count as a release MASTER_REF=master @@ -110,8 +76,6 @@ self_test() { expect 0.1.0 latest=false # feature release on master, superseded expect 0.1.1 latest=false # bugfix release off master, older line expect 0.3.0 latest=true # newest release - expect 0.1.2 FAIL # same commit as its predecessor - expect 0.2.1 FAIL # tagged on the wrong branch expect 0.2.0 FAIL # feature release not on master expect 0.1 FAIL # not MAJOR.MINOR.PATCH expect v0.1.0 FAIL # no v prefix allowed diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 4b3e610b2..3c6d8139f 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -57,33 +57,11 @@ jobs: core.setFailed(message); }; - // the command is usually posted right before hitting merge, so give the - // pull request time to land instead of failing on the race - const deadline = Date.now() + 5 * 60 * 1000; - let pr; - for (;;) { - try { - ({ data: pr } = await github.rest.pulls.get({ - owner, - repo, - pull_number: context.payload.issue.number, - })); - // a closed pull request will never merge, no point in waiting it out - if (pr.merged || pr.state === 'closed') { - break; - } - } catch (err) { - // a blip while polling must not fail the command, but a request that - // is still failing when the time is up is a real error - if (Date.now() >= deadline) { - throw err; - } - } - if (Date.now() >= deadline) { - break; - } - await new Promise((resolve) => setTimeout(resolve, 15000)); - } + const { data: pr } = await github.rest.pulls.get({ + owner, + repo, + pull_number: context.payload.issue.number, + }); if (!pr.merged) { fail(`pull request #${pr.number} is not merged`); return; @@ -179,15 +157,18 @@ jobs: TITLE: ${{ steps.pr.outputs.title }} ACTOR: ${{ github.event.comment.user.login }} run: | + # the release line the branch was cut from, 0.313 for release/0.313.1 + line=$(echo "${TARGET#release/}" | cut -d. -f1,2) + # the pull request is authored by the deploy token, so assign the # requester. the body stays empty, squashing it would carry the text # into the release branch as the commit description. - # the title stays untouched so the changelog filters and groups still - # see the original prefix, the backport label identifies the pull request + # the marker trails the title so the changelog filters and groups still + # see the original prefix, goreleaser strips it from the release notes url=$(gh pr create \ --base "$TARGET" \ --head "$BRANCH" \ - --title "$TITLE" \ + --title "$TITLE [$line backport]" \ --label backport \ --assignee "$ACTOR" \ --body "") diff --git a/.github/workflows/command-nightly.yml b/.github/workflows/command-nightly.yml index d0b1aee23..c3f2e1dc7 100644 --- a/.github/workflows/command-nightly.yml +++ b/.github/workflows/command-nightly.yml @@ -60,7 +60,6 @@ jobs: permissions: contents: read actions: read - id-token: write # depot OIDC secrets: inherit report: diff --git a/.github/workflows/nightly-docker.yml b/.github/workflows/nightly-docker.yml index 4800fe0af..9db513a49 100644 --- a/.github/workflows/nightly-docker.yml +++ b/.github/workflows/nightly-docker.yml @@ -13,7 +13,6 @@ jobs: permissions: contents: read - id-token: write # depot OIDC steps: - uses: actions/checkout@v7 @@ -28,10 +27,14 @@ jobs: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - # depot builders keep the Dockerfile's cache mounts on their own volumes, - # so they need neither buildx nor the cache dance - - name: Setup Depot - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 + - name: Setup Buildx + id: buildx + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + + - name: Restore build cache + uses: ./.github/actions/docker-cache + with: + builder: ${{ steps.buildx.outputs.name }} - name: Define tags id: meta @@ -43,9 +46,8 @@ jobs: type=raw,value=nightly.{{date 'YYYYMMDD'}}-{{sha}} - name: Publish - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.18.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 with: - project: ${{ secrets.DEPOT_PROJECT_ID }} context: . platforms: linux/amd64,linux/arm64,linux/arm/v6 push: true diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2bdbebabd..3aaacc9f1 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -48,7 +48,6 @@ jobs: permissions: contents: read actions: read - id-token: write # depot OIDC secrets: inherit hassio: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 914b468be..622d37156 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,6 @@ jobs: runs-on: depot-ubuntu-24.04-arm permissions: contents: read - id-token: write # depot OIDC steps: - uses: actions/checkout@v7 @@ -60,10 +59,14 @@ jobs: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - # depot builders keep the Dockerfile's cache mounts on their own volumes, - # so they need neither buildx nor the cache dance - - name: Setup Depot - uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 + - name: Setup Buildx + id: buildx + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 + + - name: Restore build cache + uses: ./.github/actions/docker-cache + with: + builder: ${{ steps.buildx.outputs.name }} - name: Meta id: meta @@ -76,9 +79,8 @@ jobs: latest=${{ needs.guard.outputs.latest }} - name: Publish - uses: depot/build-push-action@98e78adca7817480b8185f474a400b451d74e287 # v1.18.0 + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7 with: - project: ${{ secrets.DEPOT_PROJECT_ID }} context: . platforms: linux/amd64,linux/arm64,linux/arm/v6 push: true @@ -144,12 +146,6 @@ jobs: # a bugfix release of an older line must not move the homebrew formula # or the Github latest release marker MAKE_LATEST: ${{ needs.guard.outputs.latest }} - # macOS code signing and notarization - MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} - MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} - MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }} - MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }} - MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }} - uses: actions/setup-python@v6 with: diff --git a/.golangci.yml b/.golangci.yml index ae69e7c0f..8c1836a1e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -23,7 +23,6 @@ linters: settings: modernize: disable: - - embedlit - stringsbuilder staticcheck: checks: diff --git a/.goreleaser-nightly.yml b/.goreleaser-nightly.yml index a9a957db2..427faeedf 100644 --- a/.goreleaser-nightly.yml +++ b/.goreleaser-nightly.yml @@ -11,7 +11,8 @@ builds: - -trimpath - -tags=release ldflags: - - -X github.com/evcc-io/evcc/util.Version={{ .Version }} -X github.com/evcc-io/evcc/util.Commit={{ .ShortCommit }} -s -w + # the package version carries a timestamp for ordering, the reported version does not + - -X github.com/evcc-io/evcc/util.Version={{ if .IsSnapshot }}{{ incminor .Tag }}-dev+{{ .ShortCommit }}{{ else }}{{ .Version }}{{ end }} -X github.com/evcc-io/evcc/util.Commit={{ .ShortCommit }} -s -w env: - CGO_ENABLED=0 goos: @@ -56,9 +57,10 @@ checksum: name_template: "checksums.txt" snapshot: - # semver pre-release of the upcoming version, the timestamp keeps the package - # version increasing monotonically for apt - version_template: "{{ incminor .Version }}-dev.{{ .Timestamp }}" + # semver pre-release of the upcoming version. The timestamp makes the package + # version increase monotonically: apt compares it numerically, while the commit + # alone would sort randomly. + version_template: "{{ incminor .Version }}-dev.{{ .Timestamp }}+{{ .ShortCommit }}" changelog: sort: asc diff --git a/.goreleaser.yml b/.goreleaser.yml index 4782813ce..56c7630cf 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -16,7 +16,8 @@ builds: - -trimpath - -tags=release ldflags: - - -X github.com/evcc-io/evcc/util.Version={{ .Version }} -s -w + # the package version carries a timestamp for ordering, the reported version does not + - -X github.com/evcc-io/evcc/util.Version={{ if .IsSnapshot }}{{ incminor .Tag }}-dev+{{ .ShortCommit }}{{ else }}{{ .Version }}{{ end }} -s -w env: - CGO_ENABLED=0 goos: @@ -59,29 +60,19 @@ archives: universal_binaries: - replace: true -notarize: - macos: - - enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}' - sign: - certificate: "{{ .Env.MACOS_SIGN_P12 }}" - password: "{{ .Env.MACOS_SIGN_PASSWORD }}" - notarize: - issuer_id: "{{ .Env.MACOS_NOTARY_ISSUER_ID }}" - key_id: "{{ .Env.MACOS_NOTARY_KEY_ID }}" - key: "{{ .Env.MACOS_NOTARY_KEY }}" - wait: true - timeout: 20m - checksum: name_template: "checksums.txt" snapshot: - # semver pre-release of the upcoming version, the timestamp keeps the package - # version increasing monotonically for apt - version_template: "{{ incminor .Version }}-dev.{{ .Timestamp }}" + # semver pre-release of the upcoming version. The timestamp makes the package + # version increase monotonically: apt compares it numerically, while the commit + # alone would sort randomly. + version_template: "{{ incminor .Version }}-dev.{{ .Timestamp }}+{{ .ShortCommit }}" changelog: sort: asc + # drop the marker the backport command appends to the pull request title + format: '{{ .SHA }} {{ reReplaceAll ` \[\d+\.\d+ backport\]` .Message "" }}' filters: exclude: - "^chore" @@ -135,7 +126,7 @@ nfpms: preremove: ./packaging/scripts/preremove.sh postremove: ./packaging/scripts/postremove.sh -homebrew_casks: +brews: - skip_upload: '{{ if eq (envOrDefault "MAKE_LATEST" "true") "true" }}false{{ else }}true{{ end }}' repository: owner: evcc-io @@ -143,7 +134,15 @@ homebrew_casks: commit_author: name: andig email: info@evcc.io + directory: Formula homepage: "https://evcc.io" description: "Sonne tanken ☀️🚘" - binaries: - - evcc + license: "MIT" + test: | + system "#{bin}/evcc --version" + service: | + run [opt_bin/"evcc"] + working_dir HOMEBREW_PREFIX + keep_alive true + log_path var/"log/evcc.log" + error_log_path var/"log/evcc.log" diff --git a/.storybook/main.ts b/.storybook/main.ts index 854f1da7c..efab537f9 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,11 +1,4 @@ import { StorybookConfig } from "@storybook/vue3-vite"; -import { createReadStream } from "node:fs"; - -// serve test logos so the custom branding stories can render them -const customLogos = { - "/custom-logo-light": "tests/custom-logo-light.svg", - "/custom-logo-dark": "tests/custom-logo-dark.svg", -}; export default { stories: ["../assets/js/**/*.stories.@(js|ts)"], @@ -17,21 +10,4 @@ export default { core: { disableTelemetry: true, }, - viteFinal: (config) => ({ - ...config, - plugins: [ - ...(config.plugins ?? []), - { - name: "evcc-custom-logos", - configureServer(server) { - server.middlewares.use((req, res, next) => { - const file = customLogos[req.url?.split("?")[0] as keyof typeof customLogos]; - if (!file) return next(); - res.setHeader("Content-Type", "image/svg+xml"); - createReadStream(file).pipe(res); - }); - }, - }, - ], - }), } satisfies StorybookConfig; diff --git a/.storybook/preview.ts b/.storybook/preview.ts index a9d191d0f..b38177500 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -2,7 +2,6 @@ import { type Preview, setup } from "@storybook/vue3"; import "bootstrap/dist/css/bootstrap.min.css"; import smoothscroll from "smoothscroll-polyfill"; import setupI18n from "../assets/js/i18n"; -import tooltip from "../assets/js/directives/tooltip"; import "../assets/css/app.css"; import { watchThemeChanges } from "../assets/js/theme"; @@ -36,7 +35,6 @@ export default { setup((app) => { app.use(setupI18n()); - app.directive("tooltip", tooltip); // Mock router-link for Storybook app.component("router-link", { diff --git a/AGENTS.md b/AGENTS.md index aba006103..36e0f8a8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -200,7 +200,6 @@ Deep documentation on specific subsystems is available in `docs/agents/`. Load w ### Styling & Internationalization - Use CSS Custom Properties for theming (semantic names: `--evcc-green`, `--evcc-battery`) -- No inline `style` attributes; use Bootstrap utility classes or scoped CSS (inline only for truly dynamic values) - Use existing custom media queries for responsive breakpoints - Use `$t()` function for all user-facing strings - Update both `i18n/en.json` and `i18n/de.json` for new strings diff --git a/Dockerfile b/Dockerfile index 116f98106..7dafc4a83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,8 @@ FROM --platform=$BUILDPLATFORM node:26-alpine AS node RUN apk update && apk add --no-cache make curl bash && curl -fsSL https://vite.plus | bash -# the installer only wires vp into interactive shell rc files, which RUN steps don't source. -# newer vite+ releases dropped the split directory layout, so accept both bin locations -ENV PATH="/root/.vite-plus/bin:/root/.local/share/vite-plus/bin:${PATH}" +# the installer only wires vp into interactive shell rc files, which RUN steps don't source +ENV PATH="/root/.vite-plus/bin:${PATH}" WORKDIR /build @@ -25,7 +24,7 @@ RUN make ui # STEP 2 build executable binary -FROM --platform=$BUILDPLATFORM golang:1.27.0-alpine AS builder +FROM --platform=$BUILDPLATFORM golang:1.26.3-alpine AS builder # Install git + SSL ca certificates. # Git is required for fetching the dependencies. diff --git a/FORK.md b/FORK.md deleted file mode 100644 index 4508b30fd..000000000 --- a/FORK.md +++ /dev/null @@ -1,54 +0,0 @@ -# Fork-Hinweise - -Fork von [evcc-io/evcc](https://github.com/evcc-io/evcc). - -## Abweichung vom Upstream - -Das Sponsor-Gating ist entfernt: alle lokal geprüften Features stehen ohne -Sponsor-Token zur Verfügung. Der Eingriff sitzt an einer einzigen Stelle in -`util/sponsor/auth.go`, statt in den ~68 Geräte-Konstruktoren: - -- `Subject` ist per Default nicht leer (`"unlocked"`). Damit meldet - `RedactedStatus()` einen aktiven Sponsor-Status, und das Frontend schaltet - `isSponsor` (`assets/js/views/Config.vue`) frei — Geräte-Dialoge zeigen kein - „Sponsor-Token erforderlich" mehr. -- `IsAuthorized()` liefert immer `true`. Das öffnet alle Aufrufer, u. a. die - Charger-/Vehicle-Konstruktoren, `server/modbus/proxy.go` und den - Optimizer-Gate in `core/site_optimizer.go`. -- `ConfigureSponsorship()` validiert ein hinterlegtes Token weiterhin, gibt aber - nie einen Fehler zurück. Ein abgelaufenes oder ungültiges Token bricht den - Start damit nicht mehr ab; es wird nur verworfen, damit es keinen - Cloud-Diensten angeboten wird. - -Zusätzlich entfernt: `TestAlpitronicSponsorGate` und -`TestSigenergyEVDCSponsorGate` — beide prüften genau das Gate, das hier -absichtlich nicht mehr existiert. - -## Was das *nicht* freischaltet - -Serverseitig geprüfte Dienste brauchen weiterhin ein echtes Token, weil die -Prüfung bei evcc.io stattfindet und nicht im Client: - -- Optimizer-API (`core/site_optimizer.go` sendet `Bearer `) -- Remote Access / Tunnel (`server/remote/`) -- Cloud-Fahrzeuge und Tronity (`vehicle/cloud.go`, `vehicle/tronity.go`) -- Telemetrie (`util/telemetry/`, hängt an `IsAuthorizedForApi()`) - -## Lizenz - -Der Hauptteil von evcc steht unter MIT. `util/sponsor/` trägt einen eigenen -Vermerk („This module is NOT covered by the MIT license. All rights reserved."). -Diese Änderung betrifft genau dieses Modul. - -evcc finanziert sich über Sponsoring: - -## Upstream-Sync - -``` -git remote add upstream https://github.com/evcc-io/evcc.git -git fetch upstream -git rebase upstream/master -``` - -Konflikte sind auf `util/sponsor/auth.go` beschränkt; `FORK.md` liegt bewusst -außerhalb der Upstream-Dateien. diff --git a/Makefile b/Makefile index 70a2acd9b..ff5bf1092 100644 --- a/Makefile +++ b/Makefile @@ -6,16 +6,15 @@ 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") { m = $$2; p = $$3 + 1 } else { m = $$2 + 1; p = 0 }; print $$1 "." m "." p }') -# untagged builds are semver pre-releases of the upcoming version, the build -# timestamp keeps them increasing monotonically -BUILD_TIMESTAMP := $(shell date -u '+%s') -TAG_NAME ?= $(if $(filter-out 0,$(GIT_DIST)),$(NEXT_TAG)-dev.$(BUILD_TIMESTAMP),$(GIT_TAG)) +# 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) # hide commit for releases ifeq ($(RELEASE),1) COMMIT := endif VERSION := $(if $(TAG_NAME),$(TAG_NAME),$(SHA)) +BUILD_DATE := $(shell date -u '+%Y-%m-%d_%H:%M:%S') BUILD_TAGS := -tags=release LD_FLAGS := -X github.com/evcc-io/evcc/util.Version=$(VERSION) -X github.com/evcc-io/evcc/util.Commit=$(COMMIT) -s -w BUILD_ARGS := -trimpath -ldflags='$(LD_FLAGS)' @@ -34,7 +33,7 @@ IMAGE_FILE := evcc_$(TAG_NAME).img PACKAGES = ./release # asn1-patch -GOROOT = $(shell go env GOROOT) +GOROOT := $(shell go env GOROOT) CURRDIR := $(shell pwd) default:: ui build @@ -100,7 +99,7 @@ porcelain:: test -z "$$(git status --porcelain)" || (git status; git diff; false) build:: - @echo Version: $(VERSION) $(SHA) + @echo Version: $(VERSION) $(SHA) $(BUILD_DATE) CGO_ENABLED=0 go build -v $(BUILD_TAGS) $(BUILD_ARGS) snapshot:: @@ -110,15 +109,15 @@ release:: goreleaser --clean docker:: - @echo Version: $(VERSION) $(SHA) + @echo Version: $(VERSION) $(SHA) $(BUILD_DATE) docker buildx build --platform $(PLATFORM) --tag $(DOCKER_IMAGE):$(DOCKER_TAG) --push . publish-nightly:: - @echo Version: $(VERSION) $(SHA) + @echo Version: $(VERSION) $(SHA) $(BUILD_DATE) docker buildx build --platform $(PLATFORM) --tag $(DOCKER_IMAGE):nightly --push . publish-release:: - @echo Version: $(VERSION) $(SHA) + @echo Version: $(VERSION) $(SHA) $(BUILD_DATE) docker buildx build --platform $(PLATFORM) --tag $(DOCKER_IMAGE):latest --tag $(DOCKER_IMAGE):$(VERSION) --build-arg RELEASE=1 --push . apt-nightly:: @@ -154,7 +153,7 @@ gok-update:: ${GOK} update yes soc:: - @echo Version: $(VERSION) $(SHA) + @echo Version: $(VERSION) $(SHA) $(BUILD_DATE) go build $(BUILD_TAGS) $(BUILD_ARGS) github.com/evcc-io/evcc/cmd/soc # patch asn1.go to allow Elli buggy certificates to be accepted with EEBUS diff --git a/api/api.go b/api/api.go index c5c735462..3535b9dc7 100644 --- a/api/api.go +++ b/api/api.go @@ -130,10 +130,9 @@ type ChargeRater interface { ChargedEnergy() (float64, error) } -// Identifier identifies a vehicle and is implemented by the charger. -// A charger may know more than one identity, e.g. an RFID tag and a vehicle id. +// Identifier identifies a vehicle and is implemented by the charger type Identifier interface { - Identify() ([]string, error) + Identify() (string, error) } // Authorizer authorizes a charging session by supplying RFID credentials diff --git a/api/error.go b/api/error.go index f71d16c34..2b5ffe961 100644 --- a/api/error.go +++ b/api/error.go @@ -7,28 +7,23 @@ import ( "github.com/cenkalti/backoff/v4" ) -// permanentError is a sentinel error that signals permanence to backoff while -// remaining distinguishable from the other permanent sentinels. +// permanentError is a sentinel error that keeps matching errors.Is after +// backoff has stripped the backoff.Permanent wrapper. type permanentError struct { msg string } func (e *permanentError) Error() string { return e.msg } -// As signals permanence to backoff. Wrapping the sentinel in backoff.Permanent -// instead would make errors.Is match any other permanent error, since -// backoff.PermanentError.Is matches by type rather than identity. -func (e *permanentError) As(target any) bool { - if p, ok := target.(**backoff.PermanentError); ok { - *p = &backoff.PermanentError{Err: e} - return true - } - return false +// Is matches the wrapped sentinel, too +func (e *permanentError) Is(target error) bool { + var t *permanentError + return errors.As(target, &t) && t == e } // permanent creates a permanent sentinel error func permanent(msg string) error { - return &permanentError{msg} + return backoff.Permanent(&permanentError{msg}) } // ErrNotAvailable indicates that a feature is not available diff --git a/api/error_test.go b/api/error_test.go index 3f8231789..4dfceebe1 100644 --- a/api/error_test.go +++ b/api/error_test.go @@ -8,47 +8,20 @@ import ( "github.com/stretchr/testify/assert" ) -// permanentSentinels are all errors created by permanent() -var permanentSentinels = []error{ - ErrNotAvailable, - ErrUnsupportedPlatform, - ErrSponsorRequired, - ErrMissingCredentials, - ErrMissingToken, -} - // Backoff returns permanent errors unwrapped. These must still match the sentinel. func TestPermanentSentinels(t *testing.T) { - for _, err := range permanentSentinels { + for _, tc := range []struct{ err, other error }{ + {ErrNotAvailable, ErrUnsupportedPlatform}, + {ErrUnsupportedPlatform, ErrNotAvailable}, + {ErrMissingCredentials, ErrMissingToken}, + {ErrMissingToken, ErrMissingCredentials}, + } { _, unwrapped := backoff.RetryWithData(func() (int, error) { - return 0, err + return 0, tc.err }, &backoff.StopBackOff{}) - assert.ErrorIs(t, unwrapped, err) - assert.ErrorIs(t, fmt.Errorf("wrapped: %w", unwrapped), err) - assert.ErrorIs(t, fmt.Errorf("wrapped: %w", err), err) + assert.ErrorIs(t, unwrapped, tc.err) + assert.ErrorIs(t, fmt.Errorf("wrapped: %w", unwrapped), tc.err) + assert.NotErrorIs(t, unwrapped, tc.other) } } - -// Permanent sentinels must not match each other, whether returned directly or -// unwrapped by backoff. -func TestPermanentSentinelIdentity(t *testing.T) { - for _, err := range permanentSentinels { - _, unwrapped := backoff.RetryWithData(func() (int, error) { - return 0, err - }, &backoff.StopBackOff{}) - - for _, other := range permanentSentinels { - if other == err { - continue - } - - assert.NotErrorIs(t, err, other) - assert.NotErrorIs(t, unwrapped, other) - assert.NotErrorIs(t, fmt.Errorf("wrapped: %w", err), other) - } - } - - // login required is permanent, too - assert.NotErrorIs(t, LoginRequiredError("foo"), ErrNotAvailable) -} diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go index 40e565037..eace135f8 100644 --- a/api/globalconfig/types.go +++ b/api/globalconfig/types.go @@ -64,7 +64,6 @@ type All struct { Site map[string]any Loadpoints []config.Named Circuits []config.Named - Curtailers []config.Named } type Javascript struct { diff --git a/api/implement/implementations.go b/api/implement/implementations.go index ccadc39a7..7c3ce1da1 100644 --- a/api/implement/implementations.go +++ b/api/implement/implementations.go @@ -228,7 +228,7 @@ func (i *iDimmer) Dimmed() (bool, error) { return i.dimmer1() } -func Identifier(identifier0 func() ([]string, error)) api.Identifier { +func Identifier(identifier0 func() (string, error)) api.Identifier { if identifier0 == nil { return nil } @@ -236,10 +236,10 @@ func Identifier(identifier0 func() ([]string, error)) api.Identifier { } type iIdentifier struct { - identifier0 func() ([]string, error) + identifier0 func() (string, error) } -func (i *iIdentifier) Identify() ([]string, error) { +func (i *iIdentifier) Identify() (string, error) { return i.identifier0() } @@ -378,21 +378,6 @@ func (i *iPhaseVoltages) Voltages() (float64, float64, float64, error) { return i.phaseVoltages0() } -func PowerLimiter(powerLimiter0 func() (float64, float64, error)) api.PowerLimiter { - if powerLimiter0 == nil { - return nil - } - return &iPowerLimiter{powerLimiter0} -} - -type iPowerLimiter struct { - powerLimiter0 func() (float64, float64, error) -} - -func (i *iPowerLimiter) GetMinMaxPower() (float64, float64, error) { - return i.powerLimiter0() -} - func Resurrector(resurrector0 func() error) api.Resurrector { if resurrector0 == nil { return nil diff --git a/api/mock.go b/api/mock.go index dd59b7f5f..22470c2e3 100644 --- a/api/mock.go +++ b/api/mock.go @@ -396,10 +396,10 @@ func (m *MockIdentifier) EXPECT() *MockIdentifierMockRecorder { } // Identify mocks base method. -func (m *MockIdentifier) Identify() ([]string, error) { +func (m *MockIdentifier) Identify() (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Identify") - ret0, _ := ret[0].([]string) + ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/assets/css/app.css b/assets/css/app.css index d39e45dcd..c6d9d023f 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -50,7 +50,6 @@ --evcc-gray: var(--bs-gray-medium); --evcc-gray-50: #93949e80; --evcc-gray-25: #93949e40; - --evcc-gray-15: #93949e26; --evcc-gray-10: #93949e20; --evcc-accent1: var(--evcc-dark-yellow); --evcc-accent2: var(--evcc-darker-green); @@ -952,9 +951,3 @@ input::-webkit-date-and-time-value { .sticky-bottom { bottom: calc(var(--tab-bar-height) + 0.25rem); } - -[_echarts_instance_] { - user-select: none; - -webkit-user-select: none; - -webkit-touch-callout: none; -} diff --git a/assets/js/app.ts b/assets/js/app.ts index 1649b44f4..b453c8527 100644 --- a/assets/js/app.ts +++ b/assets/js/app.ts @@ -6,7 +6,6 @@ import { VueHeadMixin, createHead } from "@unhead/vue/client"; import App from "./views/App.vue"; import setupRouter from "./router.ts"; import setupI18n from "./i18n.ts"; -import tooltip from "./directives/tooltip.ts"; import { watchThemeChanges } from "./theme.ts"; import { applyUrlSettings } from "./urlSettings.ts"; import { appDetection, sendToApp } from "./utils/native"; @@ -100,7 +99,6 @@ app.use(i18n); app.use(router); app.use(head); app.mixin(VueHeadMixin); -app.directive("tooltip", tooltip); window.app = app.mount("#app"); watchThemeChanges(); diff --git a/assets/js/components/AboutModal.stories.ts b/assets/js/components/AboutModal.stories.ts index dbeb8031d..4b23c3fb1 100644 --- a/assets/js/components/AboutModal.stories.ts +++ b/assets/js/components/AboutModal.stories.ts @@ -52,11 +52,6 @@ export default { hasUpdater: { control: "boolean" }, uploadMessage: { control: "text" }, uploadProgress: { control: "number" }, - customLogo: { control: "boolean" }, - customBrand: { control: "text" }, - customWebsite: { control: "text" }, - customEmail: { control: "text" }, - customPhone: { control: "text" }, }, } as Meta; @@ -99,22 +94,10 @@ StableUpdateWithUpdater.args = { export const Nightly = Template.bind({}); Nightly.args = { - installed: "0.304.0-dev.1712345678", - commit: "5ce7be4", + installed: "0.304.0-dev+5ce7be4", }; export const DevBuild = Template.bind({}); DevBuild.args = { installed: "0.0.0", }; - -export const CustomBranding = Template.bind({}); -CustomBranding.args = { - installed: "0.303.1", - availableVersion: "0.303.1", - customLogo: true, - customBrand: "G1GA HEMS", - customWebsite: "https://example.com/hems", - customEmail: "support@example.com", - customPhone: "+49 123 456789", -}; diff --git a/assets/js/components/AboutModal.vue b/assets/js/components/AboutModal.vue index 751187df1..b0df8929e 100644 --- a/assets/js/components/AboutModal.vue +++ b/assets/js/components/AboutModal.vue @@ -1,5 +1,5 @@