mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 18:37:09 +02:00
Compare commits
11 commits
master
...
ci-skip-un
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27abc83ac8 | ||
|
|
bd611e7f6c |
||
|
|
dd6d9b7891 | ||
|
|
4f3e12738a | ||
|
|
d3bed09363 | ||
|
|
86e6c62cf1 | ||
|
|
98e435490d | ||
|
|
579cc3c9ff |
||
|
|
596bbf91b4 | ||
|
|
16b9f60a21 | ||
|
|
afd04c742c |
5 changed files with 334 additions and 25 deletions
31
.github/workflows/coverage-on-pr.yml
vendored
31
.github/workflows/coverage-on-pr.yml
vendored
|
|
@ -15,9 +15,37 @@ jobs:
|
|||
env:
|
||||
COVERAGE_COMMENT: 'true'
|
||||
steps:
|
||||
# The pipeline skips its coverage steps when a PR touches no Go code.
|
||||
- name: Check the run produced a coverage artifact
|
||||
id: artifact
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
count=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/${{ github.event.workflow_run.id }}/artifacts?name=octocov-pr" \
|
||||
--jq '.total_count')
|
||||
echo "count=$count" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# A PR that reverts its Go changes produces no artifact, but octocov's
|
||||
# comment for the earlier head stays. Match the marker it keys off itself.
|
||||
- name: Delete the stale coverage comment
|
||||
if: steps.artifact.outputs.count == '0'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
number=$(gh api --paginate "repos/$GITHUB_REPOSITORY/pulls?state=open&per_page=100" \
|
||||
--jq ".[] | select(.head.sha == \"$HEAD_SHA\") | .number" | head -n1)
|
||||
[ -n "$number" ] || exit 0
|
||||
id=$(gh api --paginate "repos/$GITHUB_REPOSITORY/issues/$number/comments" \
|
||||
--jq '.[] | select(.user.login == "github-actions[bot]" and (.body | contains("<!-- octocov -->"))) | .id' | head -n1)
|
||||
if [ -n "$id" ]; then
|
||||
gh api -X DELETE "repos/$GITHUB_REPOSITORY/issues/comments/$id"
|
||||
fi
|
||||
|
||||
# Only the config, from the base branch: this job holds a write token, so
|
||||
# it must never check out the fork.
|
||||
- name: Check out the octocov config
|
||||
if: steps.artifact.outputs.count != '0'
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
sparse-checkout: .octocov.yml
|
||||
|
|
@ -27,6 +55,7 @@ jobs:
|
|||
# Into a subdirectory. A pull_request run executes the fork's own copy of
|
||||
# pipeline.yml, so every file in here is attacker-controlled.
|
||||
- uses: actions/download-artifact@v8
|
||||
if: steps.artifact.outputs.count != '0'
|
||||
with:
|
||||
name: octocov-pr
|
||||
path: untrusted
|
||||
|
|
@ -35,6 +64,7 @@ jobs:
|
|||
|
||||
- name: Verify the artifact and take the coverage profile
|
||||
id: pr
|
||||
if: steps.artifact.outputs.count != '0'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
|
|
@ -51,6 +81,7 @@ jobs:
|
|||
echo "number=$number" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: k1LoW/octocov-action@v1
|
||||
if: steps.artifact.outputs.count != '0'
|
||||
env:
|
||||
# A workflow_run job looks like a push to the default branch. Point
|
||||
# octocov back at the pull request and at the run that produced it.
|
||||
|
|
|
|||
68
.github/workflows/detect-changes.sh
vendored
Executable file
68
.github/workflows/detect-changes.sh
vendored
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Emits per-area change flags to $GITHUB_OUTPUT so the pipeline can skip work a
|
||||
# pull request cannot affect:
|
||||
#
|
||||
# go - Go sources, module files, linter config, embedded resources and
|
||||
# the go:generate inputs whose generated output CI verifies
|
||||
# js - anything under ui/
|
||||
# i18n - translation files and their validation script
|
||||
# build - anything that ends up in a binary, image or package (i.e. every
|
||||
# change except the doc-only paths in $DOC_ONLY_RE)
|
||||
#
|
||||
# pipeline.yml and this script are inputs to every suite they gate: a wrong
|
||||
# gating expression skips a suite green, in every run, with no other signal.
|
||||
#
|
||||
# Only pull requests are narrowed. Master pushes, tags and manual runs always get
|
||||
# every flag, so a release can never be built from a partially validated tree.
|
||||
#
|
||||
# Flags gate STEPS, not jobs: a job-level skip propagates through the needs
|
||||
# chain (actions/runner#491) and would take the release jobs down with it.
|
||||
#
|
||||
# Compares HEAD against $BASE_REF (default master). Requires full history
|
||||
# (fetch-depth: 0 in CI).
|
||||
set -uo pipefail
|
||||
export LC_ALL=C
|
||||
|
||||
GO_RE='(\.go$|(^|/)go\.(mod|sum)$|^Makefile$|^\.golangci\.yml$|^resources/|^db/migrations/|^tests/|^plugins/manifest-schema\.json$|^\.github/workflows/(pipeline\.yml|detect-changes\.sh)$)'
|
||||
JS_RE='(^ui/|^\.github/workflows/(pipeline\.yml|detect-changes\.sh)$)'
|
||||
I18N_RE='(^resources/i18n/|^ui/src/i18n/en\.json$|^\.github/workflows/validate-translations\.sh$|^\.github/workflows/(pipeline\.yml|detect-changes\.sh)$)'
|
||||
DOC_ONLY_RE='(\.md$|^LICENSE$|^\.git-blame-ignore-revs$|^\.gitignore$|^\.devcontainer/)'
|
||||
|
||||
emit() { printf '%s=%s\n' "$1" "$2" | tee -a "${GITHUB_OUTPUT:-/dev/null}"; }
|
||||
|
||||
if [ "${GITHUB_EVENT_NAME:-}" != "pull_request" ]; then
|
||||
echo "Not a pull request — running everything."
|
||||
for area in go js i18n build; do emit "$area" true; done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BASE_REF="${BASE_REF:-master}"
|
||||
git fetch --no-tags --quiet origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" || true
|
||||
# Guard the diff, not the fetch: checkout already created the ref, so a failed
|
||||
# refresh is harmless, but an unresolvable ref would emit every flag as false.
|
||||
if ! git rev-parse --verify --quiet "origin/${BASE_REF}" >/dev/null; then
|
||||
printf '::error::Cannot resolve origin/%s. In CI, check out with fetch-depth: 0.\n' "$BASE_REF" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --no-renames: rename detection reports only the destination, so moving a file
|
||||
# out of a gated area would drop the source path from every filter.
|
||||
files="$(git diff --no-renames --name-only "origin/${BASE_REF}...HEAD")"
|
||||
echo "Changed files:"
|
||||
printf '%s\n' "$files" | sed 's/^/ /'
|
||||
echo
|
||||
|
||||
flag() { # $1=name $2=regex
|
||||
if grep -qE "$2" <<< "$files"; then emit "$1" true; else emit "$1" false; fi
|
||||
}
|
||||
|
||||
flag go "$GO_RE"
|
||||
flag js "$JS_RE"
|
||||
flag i18n "$I18N_RE"
|
||||
|
||||
if [ -n "$(grep -vE "$DOC_ONLY_RE" <<< "$files")" ]; then
|
||||
emit build true
|
||||
else
|
||||
emit build false
|
||||
fi
|
||||
110
.github/workflows/detect-changes_test.sh
vendored
Executable file
110
.github/workflows/detect-changes_test.sh
vendored
Executable file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Tests detect-changes.sh against a throwaway repo: builds a synthetic PR for
|
||||
# each change shape and asserts the four emitted flags.
|
||||
#
|
||||
# ./.github/workflows/detect-changes_test.sh # tests the sibling script
|
||||
# ./.github/workflows/detect-changes_test.sh <path> # tests another copy
|
||||
#
|
||||
# To confirm a case still bites, edit a pattern out of detect-changes.sh and
|
||||
# re-run: exactly the case that covers it should fail.
|
||||
set -uo pipefail
|
||||
SCRIPT="${1:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/detect-changes.sh}"
|
||||
T=$(mktemp -d); O=$(mktemp -d)
|
||||
cd "$T"
|
||||
git init -q -b master .
|
||||
git config user.email t@t; git config user.name t
|
||||
mkdir -p ui/src/i18n resources/i18n .github/workflows db/migrations
|
||||
echo x > README.md; echo x > main.go; echo x > ui/src/a.js
|
||||
echo x > resources/i18n/pt.json; echo x > ui/src/i18n/en.json; echo x > go.mod
|
||||
git add -A; git commit -qm base
|
||||
git clone -q --bare . "$O/origin.git"
|
||||
git remote add origin "$O/origin.git"
|
||||
git fetch -q origin
|
||||
|
||||
fails=0
|
||||
run() { # $1=label $2=expected "go js i18n build" ; rest=files
|
||||
label="$1"; want="$2"; shift 2
|
||||
git checkout -q -B test master
|
||||
for f in "$@"; do mkdir -p "$(dirname "$f")"; echo change >> "$f"; done
|
||||
git add -A >/dev/null; git commit -qm "$label"
|
||||
got=$(GITHUB_EVENT_NAME=pull_request BASE_REF=master bash "$SCRIPT" 2>&1 \
|
||||
| grep -E '^(go|js|i18n|build)=' | cut -d= -f2 | tr '\n' ' ' | sed 's/ $//')
|
||||
if [ "$got" = "$want" ]; then printf 'ok %-32s %s\n' "$label" "$got"
|
||||
else printf 'FAIL %-32s got[%s] want[%s]\n' "$label" "$got" "$want"; fails=$((fails+1)); fi
|
||||
}
|
||||
|
||||
# go js i18n build
|
||||
run "docs only" "false false false false" README.md
|
||||
run "gitignore only" "false false false false" .gitignore
|
||||
run "go only" "true false false true" core/thing.go
|
||||
run "ui only" "false true false true" ui/src/b.js
|
||||
run "i18n resources" "true false true true" resources/i18n/fr.json
|
||||
run "ui en.json" "false true true true" ui/src/i18n/en.json
|
||||
run "ui other i18n" "false true false true" ui/src/i18n/provider.js
|
||||
run "db migration sql" "true false false true" db/migrations/20260101000000_x.sql
|
||||
run "tests fixture" "true false false true" tests/fixtures/playlist.m3u
|
||||
run "tests toml" "true false false true" tests/navidrome-test.toml
|
||||
run "conf testdata" "false false false true" conf/testdata/cfg.toml
|
||||
run "manifest schema" "true false false true" plugins/manifest-schema.json
|
||||
run "other plugin json" "false false false true" plugins/testdata/fake/manifest-schema.json
|
||||
run "nested go.mod" "true false false true" plugins/testdata/x/go.mod
|
||||
run "Dockerfile only" "false false false true" Dockerfile
|
||||
run "pipeline.yml" "true true true true" .github/workflows/pipeline.yml
|
||||
run "detect-changes.sh" "true true true true" .github/workflows/detect-changes.sh
|
||||
run "other workflow" "false false false true" .github/workflows/stale.yml
|
||||
run "validate-trans.sh" "false false true true" .github/workflows/validate-translations.sh
|
||||
|
||||
echo "--- large diff must not lose flags to SIGPIPE ---"
|
||||
git checkout -q -B test master
|
||||
mkdir -p big/pkg
|
||||
python3 -c "
|
||||
import os
|
||||
os.makedirs('big/pkg', exist_ok=True)
|
||||
open('big/pkg/aaa_first.go','w').write('x')
|
||||
for i in range(2500): open('big/pkg/filler_%04d.txt' % i,'w').write('x')
|
||||
"
|
||||
git add -A >/dev/null; git commit -qm big
|
||||
nfiles=$(git diff --no-renames --name-only master...HEAD | wc -l | tr -d ' ')
|
||||
for i in 1 2 3 4 5; do
|
||||
got=$(GITHUB_EVENT_NAME=pull_request BASE_REF=master bash "$SCRIPT" 2>&1 \
|
||||
| grep -E '^(go|js|i18n|build)=' | cut -d= -f2 | tr '\n' ' ' | sed 's/ $//')
|
||||
if [ "$got" = "true false false true" ]; then printf 'ok %-32s %s (%s files)\n' "large diff run $i" "$got" "$nfiles"
|
||||
else printf 'FAIL %-32s got[%s] want[true false false true] (%s files)\n' "large diff run $i" "$got" "$nfiles"; fails=$((fails+1)); fi
|
||||
done
|
||||
|
||||
echo "--- renames must count the source path ---"
|
||||
rn() { # $1=label $2=expected $3=from $4=to
|
||||
git checkout -q -B test master
|
||||
mkdir -p "$(dirname "$4")"; git mv "$3" "$4"
|
||||
git add -A >/dev/null; git commit -qm "$1"
|
||||
got=$(GITHUB_EVENT_NAME=pull_request BASE_REF=master bash "$SCRIPT" 2>&1 \
|
||||
| grep -E '^(go|js|i18n|build)=' | cut -d= -f2 | tr '\n' ' ' | sed 's/ $//')
|
||||
if [ "$got" = "$2" ]; then printf 'ok %-32s %s\n' "$1" "$got"
|
||||
else printf 'FAIL %-32s got[%s] want[%s]\n' "$1" "$got" "$2"; fails=$((fails+1)); fi
|
||||
}
|
||||
# go js i18n build
|
||||
rn "ui .js -> docs .md" "false true false true" ui/src/a.js docs/a.js.md
|
||||
rn "go -> docs .md" "true false false true" main.go docs/main.go.md
|
||||
|
||||
echo "--- non-PR events ---"
|
||||
git checkout -q master
|
||||
for ev in push workflow_dispatch; do
|
||||
got=$(GITHUB_EVENT_NAME=$ev bash "$SCRIPT" 2>&1 | grep -E '^(go|js|i18n|build)=' | cut -d= -f2 | tr '\n' ' ' | sed 's/ $//')
|
||||
if [ "$got" = "true true true true" ]; then printf 'ok %-32s %s\n' "$ev" "$got"
|
||||
else printf 'FAIL %-32s got[%s]\n' "$ev" "$got"; fails=$((fails+1)); fi
|
||||
done
|
||||
|
||||
echo "--- unresolvable base ref must fail closed ---"
|
||||
git checkout -q -B test master; echo x >> main.go; git add -A >/dev/null; git commit -qm x
|
||||
out=$(GITHUB_EVENT_NAME=pull_request BASE_REF=does-not-exist bash "$SCRIPT" 2>&1); rc=$?
|
||||
if [ "$rc" != "0" ] && ! grep -qE '^(go|js|i18n|build)=' <<<"$out"; then
|
||||
printf 'ok %-32s exit=%s, no flags emitted\n' "bad base ref" "$rc"
|
||||
else
|
||||
printf 'FAIL %-32s exit=%s out[%s]\n' "bad base ref" "$rc" "$out"; fails=$((fails+1))
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "failures: $fails"
|
||||
cd /; rm -rf "$T" "$O"
|
||||
exit "$fails"
|
||||
21
.github/workflows/download-link-on-pr.yml
vendored
21
.github/workflows/download-link-on-pr.yml
vendored
|
|
@ -35,18 +35,27 @@ jobs:
|
|||
|
||||
const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
|
||||
const downloadable = artifacts.filter((art) => !art.name.startsWith('octocov-'));
|
||||
if (!downloadable.length) {
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
const header = `Download the artifacts for this pull request:`;
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {repo, owner, issue_number});
|
||||
// Match on the body too: octocov also comments as github-actions[bot].
|
||||
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]' && c.body.startsWith(header));
|
||||
|
||||
// A PR that changes nothing reaching a binary builds nothing. Delete
|
||||
// rather than reword: the matcher above keys off the header.
|
||||
if (!downloadable.length) {
|
||||
if (existing_comment) {
|
||||
core.info(`Deleting stale comment ${existing_comment.id}`);
|
||||
await github.rest.issues.deleteComment({repo, owner, comment_id: existing_comment.id});
|
||||
}
|
||||
return core.info(`No artifacts found`);
|
||||
}
|
||||
|
||||
let body = `${header}\n`;
|
||||
for (const art of downloadable) {
|
||||
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
}
|
||||
|
||||
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});
|
||||
// Match on the body too: octocov also comments as github-actions[bot].
|
||||
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]' && c.body.startsWith(header));
|
||||
if (existing_comment) {
|
||||
core.info(`Updating comment ${existing_comment.id}`);
|
||||
await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
|
||||
|
|
|
|||
131
.github/workflows/pipeline.yml
vendored
131
.github/workflows/pipeline.yml
vendored
|
|
@ -8,6 +8,7 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ startsWith(github.ref, 'refs/tags/v') && 'tag' || 'branch' }}-${{ github.ref }}
|
||||
|
|
@ -58,13 +59,36 @@ jobs:
|
|||
echo "GIT_TAG=$GIT_TAG"
|
||||
echo "GIT_SHA=$GIT_SHA"
|
||||
|
||||
# Outputs gate steps, never jobs: a job-level skip propagates through the needs
|
||||
# chain (actions/runner#491) and would skip all release jobs on tag pushes.
|
||||
changes:
|
||||
name: Detect changed areas
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
go: ${{ steps.detect.outputs.go }}
|
||||
js: ${{ steps.detect.outputs.js }}
|
||||
i18n: ${{ steps.detect.outputs.i18n }}
|
||||
build: ${{ steps.detect.outputs.build }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Detect changed areas
|
||||
id: detect
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
run: ./.github/workflows/detect-changes.sh
|
||||
|
||||
go-lint:
|
||||
name: Lint Go code
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
|
|
@ -72,9 +96,11 @@ jobs:
|
|||
# cannot turn red in CI just because a new golangci-lint was released.
|
||||
- name: Resolve golangci-lint version
|
||||
id: golangci-version
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: echo "version=$(grep '^GOLANGCI_LINT_VERSION' Makefile | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: golangci-lint
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: ${{ steps.golangci-version.outputs.version }}
|
||||
|
|
@ -82,9 +108,12 @@ jobs:
|
|||
args: --timeout 2m
|
||||
|
||||
- name: Run go goimports
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: go run golang.org/x/tools/cmd/goimports@latest -w `find . -name '*.go' | grep -v '_gen.go$' | grep -v '.pb.go$'`
|
||||
- run: go mod tidy
|
||||
- if: needs.changes.outputs.go == 'true'
|
||||
run: go mod tidy
|
||||
- name: Verify no changes from goimports and go mod tidy
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: |
|
||||
git status --porcelain
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
|
|
@ -93,8 +122,10 @@ jobs:
|
|||
fi
|
||||
|
||||
- name: Run go generate
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: go generate ./...
|
||||
- name: Verify no changes from go generate
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: |
|
||||
git status --porcelain
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
|
|
@ -126,23 +157,29 @@ jobs:
|
|||
go:
|
||||
name: Test Go code
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes]
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Download dependencies
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: go mod download
|
||||
|
||||
# Name must stay unique across the workflow: octocov matches step names
|
||||
# by name across every job, and waits for each match to finish.
|
||||
- name: Test with coverage
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: go test -shuffle=on -tags netgo,sqlite_fts5 -race -v -covermode=atomic -coverprofile=coverage.out $(go list ./... | grep -v '/plugins$')
|
||||
|
||||
- name: Test ndpgen
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: |
|
||||
cd plugins/cmd/ndpgen
|
||||
go test -shuffle=on -v
|
||||
|
|
@ -150,6 +187,7 @@ jobs:
|
|||
./ndpgen --help
|
||||
|
||||
- name: Upload coverage profile
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: octocov-go
|
||||
|
|
@ -159,18 +197,22 @@ jobs:
|
|||
go-plugins:
|
||||
name: Test Go plugins
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes]
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
id: setup-go
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# Without this, the suite recompiles every test plugin WASM module,
|
||||
# which dominates its runtime under -race.
|
||||
- name: Cache the WASM compilation cache
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: plugins/testdata/.wazero-cache
|
||||
|
|
@ -178,9 +220,11 @@ jobs:
|
|||
restore-keys: wazero-${{ runner.os }}-
|
||||
|
||||
- name: Test plugins
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: go tool ginkgo -p -race -tags netgo,sqlite_fts5 --cover --covermode=atomic --coverprofile=coverage.out --output-dir=. ./plugins/
|
||||
|
||||
- name: Upload coverage profile
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: octocov-plugins
|
||||
|
|
@ -190,7 +234,7 @@ jobs:
|
|||
coverage:
|
||||
name: Report coverage
|
||||
runs-on: ubuntu-latest
|
||||
needs: [go, go-plugins]
|
||||
needs: [changes, go, go-plugins]
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
|
@ -198,27 +242,31 @@ jobs:
|
|||
COVERAGE_COMMENT: 'false'
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
with:
|
||||
pattern: octocov-*
|
||||
|
||||
# Merge here rather than letting octocov do it: octocov reports statement
|
||||
# coverage for a single profile, but switches to line counting for several.
|
||||
- name: Merge coverage profiles
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
run: |
|
||||
echo "mode: atomic" > coverage.out
|
||||
awk 'FNR==1 && /^mode:/ {next} {k=$1" "$2; c[k]+=$3} END {for (k in c) print k, c[k]}' \
|
||||
octocov-*/coverage.out | sort >> coverage.out
|
||||
|
||||
- uses: k1LoW/octocov-action@v1
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
|
||||
- name: Save the PR number for the comment workflow
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && needs.changes.outputs.go == 'true'
|
||||
run: echo "${{ github.event.pull_request.number }}" > pr_number
|
||||
|
||||
- name: Upload the merged profile for the comment workflow
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request' && needs.changes.outputs.go == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: octocov-pr
|
||||
|
|
@ -230,27 +278,33 @@ jobs:
|
|||
go-windows:
|
||||
name: Test Go code (Windows)
|
||||
runs-on: windows-2022
|
||||
needs: [changes]
|
||||
env:
|
||||
FFMPEG_VERSION: "7.1"
|
||||
FFMPEG_REPOSITORY: navidrome/ffmpeg-windows-builds
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
with:
|
||||
msystem: MINGW64
|
||||
install: mingw-w64-x86_64-gcc
|
||||
update: false
|
||||
|
||||
- name: Add mingw64 to PATH
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
shell: bash
|
||||
run: echo "C:/msys64/mingw64/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Cache ffmpeg
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
id: ffmpeg-cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
|
|
@ -258,7 +312,7 @@ jobs:
|
|||
key: ffmpeg-${{ env.FFMPEG_VERSION }}-win64
|
||||
|
||||
- name: Download ffmpeg
|
||||
if: steps.ffmpeg-cache.outputs.cache-hit != 'true'
|
||||
if: needs.changes.outputs.go == 'true' && steps.ffmpeg-cache.outputs.cache-hit != 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$asset = "ffmpeg-n${env:FFMPEG_VERSION}-latest-win64-gpl-${env:FFMPEG_VERSION}"
|
||||
|
|
@ -270,10 +324,12 @@ jobs:
|
|||
Copy-Item "C:\ffmpeg-extracted\$asset\bin\ffprobe.exe" C:\ffmpeg\bin
|
||||
|
||||
- name: Add ffmpeg to PATH
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
shell: bash
|
||||
run: echo "C:/ffmpeg/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Verify toolchain
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
go version
|
||||
|
|
@ -283,16 +339,19 @@ jobs:
|
|||
ffprobe -version
|
||||
|
||||
- name: Download dependencies
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
shell: bash
|
||||
run: go mod download
|
||||
|
||||
- name: Test
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
CGO_ENABLED: "1"
|
||||
run: go test -shuffle=on -tags netgo,sqlite_fts5 ./... -v
|
||||
|
||||
- name: Test ndpgen
|
||||
if: needs.changes.outputs.go == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
cd plugins/cmd/ndpgen
|
||||
|
|
@ -303,32 +362,39 @@ jobs:
|
|||
js:
|
||||
name: Test JS code
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes]
|
||||
env:
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.changes.outputs.js == 'true'
|
||||
- uses: actions/setup-node@v6
|
||||
if: needs.changes.outputs.js == 'true'
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "npm"
|
||||
cache-dependency-path: "**/package-lock.json"
|
||||
|
||||
- name: npm install dependencies
|
||||
if: needs.changes.outputs.js == 'true'
|
||||
run: |
|
||||
cd ui
|
||||
npm ci
|
||||
|
||||
- name: npm lint
|
||||
if: needs.changes.outputs.js == 'true'
|
||||
run: |
|
||||
cd ui
|
||||
npm run check-formatting && npm run lint
|
||||
|
||||
- name: npm test
|
||||
if: needs.changes.outputs.js == 'true'
|
||||
run: |
|
||||
cd ui
|
||||
npm test
|
||||
|
||||
- name: npm build
|
||||
if: needs.changes.outputs.js == 'true'
|
||||
run: |
|
||||
cd ui
|
||||
npm run build
|
||||
|
|
@ -336,9 +402,12 @@ jobs:
|
|||
i18n-lint:
|
||||
name: Lint i18n files
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changes]
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- run: |
|
||||
if: needs.changes.outputs.i18n == 'true'
|
||||
- if: needs.changes.outputs.i18n == 'true'
|
||||
run: |
|
||||
set -e
|
||||
for file in resources/i18n/*.json; do
|
||||
echo "Validating $file"
|
||||
|
|
@ -350,6 +419,7 @@ jobs:
|
|||
fi
|
||||
done
|
||||
- run: ./.github/workflows/validate-translations.sh -v
|
||||
if: needs.changes.outputs.i18n == 'true'
|
||||
|
||||
|
||||
check-push-enabled:
|
||||
|
|
@ -364,7 +434,7 @@ jobs:
|
|||
|
||||
build:
|
||||
name: Build
|
||||
needs: [js, go, go-plugins, go-windows, go-lint, i18n-lint, git-version, check-push-enabled, validate-migrations]
|
||||
needs: [changes, js, go, go-plugins, go-windows, go-lint, i18n-lint, git-version, check-push-enabled, validate-migrations]
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ linux/amd64, linux/arm64, linux/arm/v5, linux/arm/v6, linux/arm/v7, linux/386, linux/riscv64, darwin/amd64, darwin/arm64, windows/amd64, windows/386 ]
|
||||
|
|
@ -373,19 +443,23 @@ jobs:
|
|||
IS_LINUX: ${{ startsWith(matrix.platform, 'linux/') && 'true' || 'false' }}
|
||||
IS_ARMV5: ${{ matrix.platform == 'linux/arm/v5' && 'true' || 'false' }}
|
||||
IS_DOCKER_PUSH_CONFIGURED: ${{ needs.check-push-enabled.outputs.is_enabled == 'true' }}
|
||||
SHOULD_BUILD: ${{ needs.changes.outputs.build }}
|
||||
DOCKER_BUILD_SUMMARY: false
|
||||
GIT_SHA: ${{ needs.git-version.outputs.git_sha }}
|
||||
GIT_TAG: ${{ needs.git-version.outputs.git_tag }}
|
||||
steps:
|
||||
- name: Sanitize platform name
|
||||
if: env.SHOULD_BUILD == 'true'
|
||||
id: set-platform
|
||||
run: |
|
||||
PLATFORM=$(echo ${{ matrix.platform }} | tr '/' '_')
|
||||
echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
if: env.SHOULD_BUILD == 'true'
|
||||
|
||||
- name: Prepare Docker Buildx
|
||||
if: env.SHOULD_BUILD == 'true'
|
||||
uses: ./.github/actions/prepare-docker
|
||||
id: docker
|
||||
with:
|
||||
|
|
@ -395,6 +469,7 @@ jobs:
|
|||
hub_password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: Build Binaries
|
||||
if: env.SHOULD_BUILD == 'true'
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
|
|
@ -408,14 +483,14 @@ jobs:
|
|||
GIT_TAG=${{ env.GIT_TAG }}
|
||||
|
||||
- name: Set up QEMU for smoke test
|
||||
if: env.IS_LINUX == 'true'
|
||||
if: env.SHOULD_BUILD == 'true' && env.IS_LINUX == 'true'
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
# The binary is static, so binfmt+qemu runs it directly on the runner.
|
||||
# Catches startup crashes in cross-compiled binaries before they ship,
|
||||
# e.g. the broken ifunc relocations on 32-bit arm from issue #5738.
|
||||
- name: Smoke-test binary
|
||||
if: env.IS_LINUX == 'true'
|
||||
if: env.SHOULD_BUILD == 'true' && env.IS_LINUX == 'true'
|
||||
run: |
|
||||
BIN=./output/${{ env.PLATFORM }}/navidrome
|
||||
chmod +x "$BIN"
|
||||
|
|
@ -423,6 +498,7 @@ jobs:
|
|||
echo "OK: ${{ matrix.platform }} binary starts"
|
||||
|
||||
- name: Upload Binaries
|
||||
if: env.SHOULD_BUILD == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: navidrome-${{ env.PLATFORM }}
|
||||
|
|
@ -431,7 +507,7 @@ jobs:
|
|||
|
||||
- name: Build and push image by digest
|
||||
id: push-image
|
||||
if: env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true' && env.IS_ARMV5 == 'false'
|
||||
if: env.SHOULD_BUILD == 'true' && env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true' && env.IS_ARMV5 == 'false'
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
|
|
@ -446,7 +522,7 @@ jobs:
|
|||
type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
if: env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true' && env.IS_ARMV5 == 'false'
|
||||
if: env.SHOULD_BUILD == 'true' && env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true' && env.IS_ARMV5 == 'false'
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.push-image.outputs.digest }}"
|
||||
|
|
@ -454,7 +530,7 @@ jobs:
|
|||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v7
|
||||
if: env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true' && env.IS_ARMV5 == 'false'
|
||||
if: env.SHOULD_BUILD == 'true' && env.IS_LINUX == 'true' && env.IS_DOCKER_PUSH_CONFIGURED == 'true' && env.IS_ARMV5 == 'false'
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM }}
|
||||
path: /tmp/digests/*
|
||||
|
|
@ -467,8 +543,8 @@ jobs:
|
|||
contents: read
|
||||
packages: write
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, check-push-enabled]
|
||||
if: needs.check-push-enabled.outputs.is_enabled == 'true'
|
||||
needs: [changes, build, check-push-enabled]
|
||||
if: needs.check-push-enabled.outputs.is_enabled == 'true' && needs.changes.outputs.build == 'true'
|
||||
env:
|
||||
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
|
||||
steps:
|
||||
|
|
@ -502,8 +578,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
needs: [build, check-push-enabled]
|
||||
if: needs.check-push-enabled.outputs.is_enabled == 'true' && vars.DOCKER_HUB_REPO != ''
|
||||
needs: [changes, build, check-push-enabled]
|
||||
if: needs.check-push-enabled.outputs.is_enabled == 'true' && vars.DOCKER_HUB_REPO != '' && needs.changes.outputs.build == 'true'
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
|
|
@ -555,22 +631,26 @@ jobs:
|
|||
|
||||
msi:
|
||||
name: Build Windows installers
|
||||
needs: [build, git-version]
|
||||
needs: [changes, build, git-version]
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
with:
|
||||
path: ./binaries
|
||||
pattern: navidrome-windows*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Install Wix
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
run: sudo apt-get install -y wixl jq
|
||||
|
||||
- name: Build MSI
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
env:
|
||||
GIT_TAG: ${{ needs.git-version.outputs.git_tag }}
|
||||
run: |
|
||||
|
|
@ -580,6 +660,7 @@ jobs:
|
|||
du -h binaries/msi/*.msi
|
||||
|
||||
- name: Upload MSI files
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: navidrome-windows-installers
|
||||
|
|
@ -588,29 +669,33 @@ jobs:
|
|||
|
||||
release:
|
||||
name: Package/Release
|
||||
needs: [build, msi]
|
||||
needs: [changes, build, msi]
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
package_list: ${{ steps.set-package-list.outputs.package_list }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
with:
|
||||
path: ./binaries
|
||||
pattern: navidrome-*
|
||||
merge-multiple: true
|
||||
|
||||
- run: ls -lR ./binaries
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
|
||||
- name: Set RELEASE_FLAGS for snapshot releases
|
||||
if: env.IS_RELEASE == 'false'
|
||||
if: needs.changes.outputs.build == 'true' && env.IS_RELEASE == 'false'
|
||||
run: echo 'RELEASE_FLAGS=--skip=publish --snapshot' >> $GITHUB_ENV
|
||||
|
||||
- name: Run GoReleaser
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
version: '2.16.0'
|
||||
|
|
@ -619,17 +704,20 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Remove build artifacts
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
run: |
|
||||
ls -l ./dist
|
||||
rm ./dist/*.tar.gz ./dist/*.zip
|
||||
|
||||
- name: Upload all-packages artifact
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: packages
|
||||
path: dist/navidrome_0*
|
||||
|
||||
- id: set-package-list
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
name: Export list of generated packages
|
||||
run: |
|
||||
cd dist
|
||||
|
|
@ -641,7 +729,10 @@ jobs:
|
|||
upload-packages:
|
||||
name: Upload Linux PKG
|
||||
runs-on: ubuntu-latest
|
||||
needs: [release]
|
||||
needs: [changes, release]
|
||||
# Job-level skip is safe here: nothing needs this job, and fromJson would
|
||||
# error on the empty package_list a skipped release leaves behind.
|
||||
if: needs.changes.outputs.build == 'true'
|
||||
strategy:
|
||||
matrix:
|
||||
item: ${{ fromJson(needs.release.outputs.package_list) }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue