Initial commit

Based on JMusicBot by jagrosh (https://github.com/jagrosh/MusicBot),
via the maintained fork by arif-banai (https://github.com/arif-banai/MusicBot).
See NOTICE for attribution. Licensed under Apache 2.0.

YouTube-side changes had broken playback entirely. Fixes:

- Apply the remote signature cipher independently of OAuth. Deciphering and
  authentication are unrelated, but the cipher was only configured when OAuth
  was enabled, so the default install fell back to the bundled extractor and
  failed with "Must find sig function from script: /s/player/<id>/...".
  Now always applied, and configurable via playback.youtube.remoteCipher.url.

- Add the ANDROID client and order the client list by reliability. ANDROID is
  currently the only client YouTube reliably streams to anonymously. WEB moves
  last, as YouTube answers it with SABR-only responses carrying no playable
  format ("No supported audio streams available, available types:").

- Detect sign-in failures across the whole exception graph. The existing hint
  could never fire: it matched the aggregate AllClientsFailedException message
  rather than the per-client reasons, and compared against an ASCII apostrophe
  while YouTube sends U+2019.

- Track youtube-source past 1.18.2, which predates the TV client User-Agent fix
  and the contentLength recovery fix.

Measured on previously failing tracks: 5/5 playing, stable across repeat runs
(was 1/5). No OAuth required. Adds 17 tests; 676 unit + 48 integration pass.
This commit is contained in:
purnell569 committed 2026-09-11 13:47:20 +09:30
commit 0941987234
295 files changed
+58207

No files matched your search

+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at j@gro.sh. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: arif-banai
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+64
View File
@@ -0,0 +1,64 @@
name: Bug Report
description: Report a bug or unexpected behavior
title: "[Bug Report] Title Here "
labels: []
assignees: []
body:
- type: textarea
id: description
attributes:
label: Bug Description
description: Please provide a clear and concise description of what the bug is.
placeholder: The bug is...
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Steps to Reproduce
description: Provide steps you can take to reliably reproduce this bug
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Result
description: What did you expect to happen when this bug occurred?
placeholder: I expected...
validations:
required: true
- type: textarea
id: debug
attributes:
label: Debug Output
description: Please run the `debug` command and paste it here or upload it as an attachment. If you cannot run the command, please provide information about your system such as operating system version, JMusicBot version, etc.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Info
description: Any additional info that you think might be useful or relevant to this bug
- type: checkboxes
id: checks
attributes:
label: Checklist
description: Before submitting your issue, please affirm the following
options:
- label: I have looked for information about this [within the documentation](https://jmusicbot.com/)
required: true
- label: I have searched for similar issues [on the issues page](https://github.com/arif-banai/MusicBot/issues?q=is%3Aissue)
required: true
- label: I am using an official release from [the releases page](https://github.com/arif-banai/MusicBot/releases)
required: true
- type: input
id: version
attributes:
label: JMusicBot Version
description: Which version of JMusicBot are you running?
validations:
required: true
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/arif-banai/MusicBot/discussions/categories/general
about: For general questions, please check the Discussions
- name: Troubleshooting
url: https://github.com/arif-banai/MusicBot/discussions/categories/help
about: For troubleshooting help, please check the Discussions
@@ -0,0 +1,52 @@
name: Feature Request
description: Suggest an idea for this project
title: "[Feature Request] Title Here"
labels: []
assignees: []
body:
- type: textarea
id: problem
attributes:
label: Is your feature request related to a problem? Please describe.
description: Please provide a clear and concise description of what the problem is.
placeholder: A common problem users face is...
validations:
required: true
- type: textarea
id: solution
attributes:
label: What is your ideal solution to the problem?
description: Describe a clear and concise description of what you want to happen.
placeholder: My solution to the above problem is...
validations:
required: true
- type: textarea
id: procedure
attributes:
label: How would this feature be used?
description: Describe the basic flow/steps of using this feature.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Info
description: Any additional info that you think might be useful or relevant to this feature request
- type: checkboxes
id: checks
attributes:
label: Checklist
description: Before submitting your issue, please affirm the following
options:
- label: I have checked [the documentation](https://jmusicbot.com/) to make sure this feature doesn't already exist
required: true
- label: I have searched for [similar feature requests](https://github.com/arif-banai/MusicBot/labels/enhancement)
required: true
- label: I have confirmed that my requests is **not** on the [list of things that will not be added](https://jmusicbot.com/things-that-wont-be-added)
required: true
- label: "I am running the latest version of the bot: ![Release](https://img.shields.io/github/release/arif-banai/MusicBot.svg)"
required: true
+14
View File
@@ -0,0 +1,14 @@
### This pull request...
- [ ] Fixes a bug
- [ ] Introduces a new feature
- [ ] Improves an existing feature
- [ ] Boosts code quality or performance
### Description
Replace this with a description of the pull request
### Purpose
Replace this with a description of the problem or use-case
### Relevant Issue(s)
This PR closes issue #...
+71
View File
@@ -0,0 +1,71 @@
name: 'Docker Build and Push'
description: 'Builds and pushes a multi-arch Docker image to GHCR with caching'
inputs:
tags:
description: 'Docker image tags (from metadata-action)'
required: true
annotations:
description: 'OCI annotations from metadata-action (for multi-arch images)'
required: true
timestamp:
description: 'Build timestamp in ISO 8601 format'
required: true
registry:
description: 'Container registry (e.g., ghcr.io)'
required: false
default: 'ghcr.io'
image-name:
description: 'Image name (e.g., arif-banai/musicbot)'
required: false
default: 'arif-banai/musicbot'
token:
description: 'GitHub token for authentication'
required: true
outputs:
digest:
description: 'Docker image digest'
value: ${{ steps.build.outputs.digest }}
runs:
using: composite
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
driver-opts: |
network=host
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ inputs.registry }}
username: ${{ github.actor }}
password: ${{ inputs.token }}
- name: Build and push Docker image
id: build
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ inputs.tags }}
annotations: ${{ inputs.annotations }}
cache-from: |
type=gha
type=registry,ref=${{ inputs.registry }}/${{ inputs.image-name }}:buildcache
cache-to: |
type=gha,mode=max
type=registry,ref=${{ inputs.registry }}/${{ inputs.image-name }}:buildcache,mode=max
platforms: linux/amd64,linux/arm64
build-args: |
BUILD_TIMESTAMP=${{ inputs.timestamp }}
- name: Image digest
shell: bash
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"
+63
View File
@@ -0,0 +1,63 @@
name: Build and Test
# Build and test on PRs and master pushes
# This workflow does NOT publish Docker images - that's handled by docker-publish.yml
on:
pull_request:
branches:
- master # PRs targeting trunk
push:
branches:
- master # Direct pushes to master
paths-ignore:
- '.github/**' # Ignore GitHub config changes (workflows, actions, templates, etc.)
- 'README.md'
- 'LICENSE'
- 'CNAME'
- 'docs/**'
- '*.example.yml'
- '*.example.yaml'
- '.circleci/**' # If not using CircleCI, ignore its config
# Cancel in-progress runs for the same workflow and branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read # Only need to checkout repository
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set up Java Environment
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'temurin'
cache: maven
- name: Build and Test with Coverage
run: |
COMMIT_TIME=$(git log -1 --format=%cI)
mvn --batch-mode verify -Pcoverage -Dproject.build.outputTimestamp=$COMMIT_TIME
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v5
with:
report_type: coverage
files: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v5
with:
report_type: test_results
token: ${{ secrets.CODECOV_TOKEN }}
+120
View File
@@ -0,0 +1,120 @@
name: Publish Docker Image
# Docker publishing workflow - gated by successful build-and-test completion
# This ensures images are only published after tests pass on master
on:
# Triggered after successful build-and-test on master
# Note: The branches filter checks workflow_run.head_branch, which is the source branch
# of the triggering workflow. For PRs targeting master, head_branch is the PR source
# branch (not master), so this filter already excludes PR-triggered builds. Only direct
# pushes to master will have head_branch == 'master' and trigger this workflow.
workflow_run:
workflows: ["Build and Test"]
types:
- completed
branches:
- master
# Also trigger on version tags (v*) for releases
push:
tags:
- 'v*'
# Cancel in-progress runs for the same workflow and ref
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: arif-banai/musicbot
jobs:
publish:
name: Publish Docker Image
runs-on: ubuntu-latest
timeout-minutes: 45
# Only run if the triggering workflow succeeded (for workflow_run events)
# For tag pushes, always run
if: |
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') ||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'))
permissions:
contents: read
packages: write # Required for pushing to GHCR
steps:
- name: Checkout repository
uses: actions/checkout@v6
# For workflow_run events, checkout the commit that triggered the original workflow
with:
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
fetch-depth: 0 # Full history needed for git commands
- name: Extract metadata
id: metadata
run: |
# Extract project version from pom.xml
POM_VERSION=$(grep -m 1 '<version>' pom.xml | sed 's/.*<version>\([^<]*\)<\/version>.*/\1/' | tr -d ' ')
SHA_SHORT=$(git rev-parse --short HEAD)
TIMESTAMP=$(git log -1 --format=%cI)
# Determine if this is a tag build or master branch build
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref_type }}" == "tag" ]]; then
# For tag builds, use the git tag version (strip 'v' prefix)
VERSION="${GITHUB_REF#refs/tags/v}"
IS_TAG_BUILD="true"
else
# For master builds, use pom.xml version
VERSION="$POM_VERSION"
IS_TAG_BUILD="false"
fi
# Check if version is a SNAPSHOT
if [[ "$VERSION" =~ -SNAPSHOT$ ]]; then
IS_SNAPSHOT="true"
else
IS_SNAPSHOT="false"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "pom_version=$POM_VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$SHA_SHORT" >> "$GITHUB_OUTPUT"
echo "timestamp=$TIMESTAMP" >> "$GITHUB_OUTPUT"
echo "is_tag_build=$IS_TAG_BUILD" >> "$GITHUB_OUTPUT"
echo "is_snapshot=$IS_SNAPSHOT" >> "$GITHUB_OUTPUT"
echo "Extracted version: $VERSION (pom: $POM_VERSION), SHA: $SHA_SHORT, is_tag: $IS_TAG_BUILD, is_snapshot: $IS_SNAPSHOT"
- name: Generate Docker metadata
id: meta
uses: docker/metadata-action@v5
env:
# For multi-arch images, annotations must be attached to the image index
# GitHub Packages reads annotations from the index level for multi-arch images
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Tag rules:
# - For tag builds: version tag (from git tag) + sha tag
# - For master builds: latest + sha + version (if not SNAPSHOT)
tags: |
type=raw,value=${{ steps.metadata.outputs.version }},enable=${{ steps.metadata.outputs.is_tag_build == 'true' }}
type=raw,value=latest,enable=${{ steps.metadata.outputs.is_tag_build == 'false' }}
type=raw,value=${{ steps.metadata.outputs.version }},enable=${{ steps.metadata.outputs.is_tag_build == 'false' && steps.metadata.outputs.is_snapshot == 'false' }}
type=raw,value=sha-${{ steps.metadata.outputs.sha }}
# Labels are automatically generated from git context by metadata-action
# No need to specify them - metadata-action will create standard OCI labels
- name: Build and push Docker image
id: build
uses: ./.github/actions/docker-build
with:
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
timestamp: ${{ steps.metadata.outputs.timestamp }}
registry: ${{ env.REGISTRY }}
image-name: ${{ env.IMAGE_NAME }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Image digest
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"
+153
View File
@@ -0,0 +1,153 @@
name: Make Release
# Manual workflow to create a GitHub release
# This sets the version, builds the JAR, and creates a release with tag
# The tag will trigger docker-publish.yml to build and push the Docker image
on:
workflow_dispatch:
inputs:
version_number:
description: 'Version Number (e.g., 0.6.3)'
required: true
type: string
info:
description: 'Description of this Release'
required: true
type: string
is_prerelease:
description: 'Mark this as a pre-release (for testing)'
required: false
type: boolean
default: false
ref:
description: 'Branch, tag, or SHA to build from (defaults to the branch this workflow is run from)'
required: false
type: string
default: ''
# Prevent concurrent releases
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
build_jar:
name: Build JAR
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write # Required to push version commit
outputs:
release_sha: ${{ steps.set-release-sha.outputs.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.ref || github.ref }}
fetch-depth: 0 # Full history needed for git commands
- name: Validate branch for regular releases
if: ${{ github.event.inputs.is_prerelease != 'true' }}
run: |
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" != "master" ]; then
echo "❌ Error: Regular releases can only be created from the 'master' branch."
echo " Current branch: $BRANCH"
echo " To create a pre-release from this branch, check the 'Mark this as a pre-release' option."
exit 1
fi
echo "✓ Branch validation passed: $BRANCH"
- name: Set up Java Environment
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'temurin'
cache: maven
- name: Get initial SHA (for pre-releases)
id: get-initial-sha
if: ${{ github.event.inputs.is_prerelease == 'true' }}
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Set Version
run: |
mvn -B versions:set -DnewVersion=${{ github.event.inputs.version_number }}
mvn -B versions:commit
- name: Commit version bump
if: ${{ github.event.inputs.is_prerelease != 'true' }}
id: commit-version
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Stage whatever the version plugin changed
git add pom.xml
# If nothing is staged, don't fail the workflow
if git diff --cached --quiet; then
echo "No version changes to commit (pom.xml already at ${{ github.event.inputs.version_number }})."
exit 0
fi
git commit -m "chore: bump version to ${{ github.event.inputs.version_number }}"
git push
- name: Set release SHA output
id: set-release-sha
run: |
if [ "${{ github.event.inputs.is_prerelease }}" == "true" ]; then
echo "sha=${{ steps.get-initial-sha.outputs.sha }}" >> "$GITHUB_OUTPUT"
else
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
fi
- name: Build with Maven
run: |
COMMIT_TIME=$(git log -1 --format=%cI)
mvn --batch-mode verify -Dproject.build.outputTimestamp=$COMMIT_TIME
- name: Rename JAR
run: mv target/*-All.jar JMusicBot-${{ github.event.inputs.version_number }}.jar
- name: Upload JAR artifact
uses: actions/upload-artifact@v6
with:
name: jar
path: JMusicBot-${{ github.event.inputs.version_number }}.jar
if-no-files-found: error
create_release:
name: Create GitHub Release
runs-on: ubuntu-latest
timeout-minutes: 10
needs: build_jar
permissions:
contents: write # Required to create releases and tags
steps:
- name: Download JAR artifact
uses: actions/download-artifact@v7
with:
name: jar
path: .
- name: Create Release
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag: v${{ github.event.inputs.version_number }}
commit: ${{ needs.build_jar.outputs.release_sha }}
name: v${{ github.event.inputs.version_number }}
draft: true
prerelease: ${{ github.event.inputs.is_prerelease }}
artifacts: "*.jar"
body: |
${{ github.event.inputs.info }}
---
### Setup
https://jmusicbot.com/setup
https://jmusicbot.com/config
# Download: [JMusicBot-${{ github.event.inputs.version_number }}.jar](https://github.com/arif-banai/MusicBot/releases/download/v${{ github.event.inputs.version_number }}/JMusicBot-${{ github.event.inputs.version_number }}.jar)
+131
View File
@@ -0,0 +1,131 @@
name: Publish Preview Image
# Optional workflow for manually publishing preview Docker images from any branch/tag/SHA
# This is separate from the main CI/CD pipeline and allows explicit preview builds
on:
workflow_dispatch:
inputs:
ref:
description: 'Git ref to build (branch name, tag, or SHA)'
required: true
type: string
tag:
description: 'Optional tag suffix override (if empty, derived from ref)'
required: false
type: string
run_tests:
description: 'Run Maven tests before building Docker image'
required: false
type: boolean
default: true
# Cancel in-progress runs for the same workflow and ref
concurrency:
group: ${{ github.workflow }}-${{ github.event.inputs.ref || github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: arif-banai/musicbot
jobs:
publish-preview:
name: Publish Preview Docker Image
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
packages: write # Required for pushing to GHCR
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.ref }}
fetch-depth: 0 # Full history needed for git commands
- name: Extract metadata
id: metadata
run: |
# Extract project version from pom.xml
VERSION=$(grep -m 1 '<version>' pom.xml | sed 's/.*<version>\([^<]*\)<\/version>.*/\1/' | tr -d ' ')
SHA_SHORT=$(git rev-parse --short HEAD)
TIMESTAMP=$(git log -1 --format=%cI)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$SHA_SHORT" >> "$GITHUB_OUTPUT"
echo "timestamp=$TIMESTAMP" >> "$GITHUB_OUTPUT"
echo "Extracted version: $VERSION, SHA: $SHA_SHORT"
- name: Set up Java Environment
# Run if run_tests is true (default) or explicitly set to true
if: ${{ github.event.inputs.run_tests != 'false' }}
uses: actions/setup-java@v5
with:
java-version: '25'
distribution: 'temurin'
cache: maven
- name: Run Maven tests
# Run if run_tests is true (default) or explicitly set to true
if: ${{ github.event.inputs.run_tests != 'false' }}
run: |
COMMIT_TIME=$(git log -1 --format=%cI)
mvn --batch-mode verify -Pdocker -Dproject.build.outputTimestamp=$COMMIT_TIME
- name: Sanitize tag suffix
id: sanitize
run: |
# Determine the tag suffix: use input.tag if provided, otherwise derive from ref
if [[ -n "${{ github.event.inputs.tag }}" ]]; then
TAG_SUFFIX="${{ github.event.inputs.tag }}"
else
# Derive from ref: strip refs/heads/, refs/tags/, refs/ prefixes
REF="${{ github.event.inputs.ref }}"
REF="${REF#refs/heads/}"
REF="${REF#refs/tags/}"
REF="${REF#refs/}"
TAG_SUFFIX="$REF"
fi
# Sanitize: lowercase, replace '/' with '-', strip invalid chars to [a-z0-9._-]
# Truncate to 50 characters to avoid tag length issues
SANITIZED=$(echo "$TAG_SUFFIX" | tr '[:upper:]' '[:lower:]' | tr '/' '-' | tr -cd '[:alnum:]._-' | cut -c1-50)
echo "suffix=$SANITIZED" >> "$GITHUB_OUTPUT"
echo "Sanitized tag suffix: $SANITIZED"
- name: Generate Docker metadata
id: meta
uses: docker/metadata-action@v5
env:
# For multi-arch images, annotations must be attached to the image index
# GitHub Packages reads annotations from the index level for multi-arch images
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Tag rules for preview images:
# - preview-<sanitized-ref> tag
# - sha-<short> immutable tag
tags: |
type=raw,value=preview-${{ steps.sanitize.outputs.suffix }}
type=raw,value=sha-${{ steps.metadata.outputs.sha }}
# Labels are automatically generated from git context by metadata-action
# No need to specify them - metadata-action will create standard OCI labels
- name: Build and push Docker image
id: build
uses: ./.github/actions/docker-build
with:
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
timestamp: ${{ steps.metadata.outputs.timestamp }}
registry: ${{ env.REGISTRY }}
image-name: ${{ env.IMAGE_NAME }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Print final tags
run: |
echo "Successfully published preview Docker image"
echo "Tags: ${{ steps.meta.outputs.tags }}"
echo "Image digest: ${{ steps.build.outputs.digest }}"
@@ -0,0 +1,69 @@
name: Validate Branch Naming
# Validates branch naming conventions for feature/fix/chore/deps/release branches
# Runs only on pull requests
on:
pull_request:
types: [opened]
# Cancel in-progress runs for the same PR
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
validate-branch-name:
name: Validate Branch Naming Convention
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Validate branch name
run: |
# Extract branch name from pull request head ref
BRANCH_NAME="${{ github.head_ref }}"
echo "Validating branch: $BRANCH_NAME"
# Check each allowed pattern
VALID=false
# feature/<slug>
if [[ "$BRANCH_NAME" =~ ^feature/[a-z0-9-]+$ ]]; then
VALID=true
echo "✓ Branch name matches pattern: feature/<slug>"
# fix/<slug>
elif [[ "$BRANCH_NAME" =~ ^fix/[a-z0-9-]+$ ]]; then
VALID=true
echo "✓ Branch name matches pattern: fix/<slug>"
# chore/<slug>
elif [[ "$BRANCH_NAME" =~ ^chore/[a-z0-9-]+$ ]]; then
VALID=true
echo "✓ Branch name matches pattern: chore/<slug>"
# deps/<slug>
elif [[ "$BRANCH_NAME" =~ ^deps/[a-z0-9-]+$ ]]; then
VALID=true
echo "✓ Branch name matches pattern: deps/<slug>"
# release/<version> (e.g., release/0.6.3 or release/0.6.3-rc1)
elif [[ "$BRANCH_NAME" =~ ^release/[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9-]+)?$ ]]; then
VALID=true
echo "✓ Branch name matches pattern: release/<version>"
fi
if [[ "$VALID" == "false" ]]; then
echo "❌ Branch name '$BRANCH_NAME' does not match allowed patterns:"
echo ""
echo "Allowed branch patterns:"
echo " - feature/<slug> (e.g., feature/new-player-ui)"
echo " - fix/<slug> (e.g., fix/youtube-oauth)"
echo " - chore/<slug> (e.g., chore/update-dependencies)"
echo " - deps/<slug> (e.g., deps/youtube-source-pr195)"
echo " - release/<version> (e.g., release/0.6.3 or release/0.6.3-rc1)"
echo ""
echo "Branch names should:"
echo " - Use lowercase letters, numbers, and hyphens only"
echo " - Follow the pattern: <type>/<descriptive-slug>"
echo ""
exit 1
fi
echo "✅ Branch name is valid!"