Compare commits

...

3 Commits

Author SHA1 Message Date
742aae923b Merge pull request 'add-release-notes' () from add-release-notes into main
Reviewed-on: 
2025-04-04 12:06:48 +00:00
dave
bcbdfef896 docs: update release notes with detailed changes for v0.1.1 2025-04-04 15:06:11 +03:00
dave
7c2a889712 fix: pipeline stages 2025-04-04 14:50:19 +03:00
2 changed files with 327 additions and 156 deletions

@ -1,17 +1,23 @@
name: Publish to Zapstore
name: Release and Publish to Zapstore
on:
# Manual trigger with version input
push:
branches:
- main
# Enable manual trigger for the entire workflow
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., 0.0.6)'
confirm_zapstore_publish:
description: 'Type YES to publish to Zapstore (required for final publishing stage)'
required: true
default: ''
default: 'YES'
jobs:
publish:
build_and_release:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
released: ${{ steps.check_release.outputs.released }}
steps:
- name: Checkout repository
@ -19,117 +25,277 @@ jobs:
with:
fetch-depth: 0
- name: Set version
id: get_version
- name: Set up JDK 17
run: |
VERSION="${{ github.event.inputs.version }}"
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Publishing version: $VERSION"
- name: Prepare release directory
run: mkdir -p ./release
- name: Download APK from release
run: |
APK_NAME="voca-${VERSION}.apk"
DOWNLOAD_URL="https://git.nostrdev.com/voca/voca/releases/download/${VERSION}/app-release.apk"
echo "VERSION: $VERSION"
echo "Downloading APK from: $DOWNLOAD_URL"
curl -L -f -o "./release/${APK_NAME}" "$DOWNLOAD_URL" || {
echo "❌ ERROR: APK not found at $DOWNLOAD_URL"
echo "Trying alternate URL with voca-${VERSION}.apk filename..."
ALTERNATE_URL="https://git.nostrdev.com/voca/voca/releases/download/${VERSION}/voca-${VERSION}.apk"
curl -L -f -o "./release/${APK_NAME}" "$ALTERNATE_URL" || {
echo "❌ ERROR: APK not found at alternate URL either: $ALTERNATE_URL"
exit 1
}
}
echo "✅ APK downloaded: $APK_NAME"
stat "./release/${APK_NAME}"
file "./release/${APK_NAME}"
- name: Install Java Runtime (JRE)
run: |
sudo apt-get update
sudo apt-get install -y default-jre
if command -v java &> /dev/null; then
echo "✅ Java is already installed"
java -version
else
echo "⚙️ Installing JDK 17..."
sudo apt-get update
sudo apt-get install -y openjdk-17-jdk
fi
if [ -d "/usr/lib/jvm/temurin-17-jdk-amd64" ]; then
echo "JAVA_HOME=/usr/lib/jvm/temurin-17-jdk-amd64" >> $GITHUB_ENV
echo "/usr/lib/jvm/temurin-17-jdk-amd64/bin" >> $GITHUB_PATH
else
echo "JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))" >> $GITHUB_ENV
echo "$(dirname $(readlink -f $(which java)))" >> $GITHUB_PATH
fi
java -version
- name: Install ZapStore CLI and apktool
- name: Setup Android SDK
uses: android-actions/setup-android@v2
- name: Install GitHub CLI
run: |
ZAP_CLI_URL="https://cdn.zapstore.dev/0d684425c4bbd3fdecc58f7bf7fc55366d71b8ded9d68b3bbfcb3fcca1072325"
curl -L -o zapstore "$ZAP_CLI_URL"
chmod +x zapstore
sudo mv zapstore /usr/local/bin/
sudo apt-get update
sudo apt-get install -y curl unzip
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | \
sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
echo "✅ ZapStore CLI installed:"
zapstore --version
echo "Installing apktool non-interactively..."
# Bypass interactive prompts by pre-creating license files
mkdir -p /tmp/android-sdk/licenses
echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > /tmp/android-sdk/licenses/android-sdk-license
echo "84831b9409646a918e30573bab4c9c91346d8abd" >> /tmp/android-sdk/licenses/android-sdk-license
echo "84831b9409646a918e30573bab4c9c91346d8abd" > /tmp/android-sdk/licenses/android-sdk-preview-license
export ANDROID_SDK_ROOT=/tmp/android-sdk
# Now try to install apktool
yes | zapstore install apktool || {
echo "❌ apktool install via zapstore failed. Falling back to manual install..."
curl -L -o apktool.jar https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.9.3.jar
curl -L -o apktool https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool
chmod +x apktool && sudo mv apktool /usr/local/bin/
sudo mv apktool.jar /usr/local/bin/
}
apktool --version || echo "apktool version command failed, but continuing"
- name: Install Android apksigner tool
- name: Install dependencies
run: |
sudo apt-get install -y unzip wget
sudo apt-get update
# Install essential tools
sudo apt-get install -y zip jq
echo "Downloading Android Build Tools 33.0.2..."
- name: Check cache server availability
id: check_cache
run: |
CACHE_URL=${{ env.ACTIONS_CACHE_URL || '' }} # Get potential cache URL from env
if [[ -z "$CACHE_URL" || ! "$CACHE_URL" =~ http://([^:]+):([0-9]+) ]]; then
echo "No valid ACTIONS_CACHE_URL set or format incorrect. Skipping check."
echo "cache_available=false" >> $GITHUB_OUTPUT
else
CACHE_HOST=${BASH_REMATCH[1]}
CACHE_PORT=${BASH_REMATCH[2]}
echo "Checking if cache server is alive at $CACHE_HOST:$CACHE_PORT..."
# Use timeout with bash network redirection
if timeout 2 bash -c "</dev/tcp/$CACHE_HOST/$CACHE_PORT" 2>/dev/null; then
echo "Cache server seems available."
echo "cache_available=true" >> $GITHUB_OUTPUT
else
echo "Cache server unreachable or timed out. Disabling cache."
echo "cache_available=false" >> $GITHUB_OUTPUT
fi
fi
- name: Restore Gradle cache
# Only run if the check passed
if: steps.check_cache.outputs.cache_available == 'true'
# Add a safety timeout
timeout-minutes: 1
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-cache-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-cache-${{ runner.os }}-
# Allow step to continue without error if cache fails/times out
continue-on-error: true
- name: Set up Gradle
run: |
chmod +x ./gradlew
# Verify Gradle version
./gradlew --version
- name: Create local.properties
run: |
echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties
cat local.properties
- name: Set up keystore
env:
RELEASE_KEYSTORE: ${{ secrets.RELEASE_KEYSTORE }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
mkdir -p app
echo "$RELEASE_KEYSTORE" | base64 -d > app/keystore.jks
test -f app/keystore.jks || { echo "❌ Keystore file missing after decode"; exit 1; }
echo "storeFile=$(pwd)/app/keystore.jks" > keystore.properties
echo "storePassword=$KEYSTORE_PASSWORD" >> keystore.properties
echo "keyAlias=$KEY_ALIAS" >> keystore.properties
echo "keyPassword=$KEY_PASSWORD" >> keystore.properties
ls -la app/keystore.jks keystore.properties
- name: Download Android Build Tools for signing
run: |
mkdir -p /tmp/android-sdk
wget -q https://dl.google.com/android/repository/build-tools_r33.0.2-linux.zip -O /tmp/build-tools.zip
unzip -q /tmp/build-tools.zip -d /tmp/android-sdk
export ANDROID_BUILD_TOOLS=$(find /tmp/android-sdk -mindepth 1 -maxdepth 1 -type d | head -n 1)
echo "ANDROID_BUILD_TOOLS=$ANDROID_BUILD_TOOLS" >> $GITHUB_ENV
echo "$ANDROID_BUILD_TOOLS" >> $GITHUB_PATH
chmod +x $ANDROID_BUILD_TOOLS/*
unzip -q /tmp/build-tools.zip -d /tmp/build-tools
export APKSIGNER_PATH=$(find /tmp/build-tools -name apksigner -type f | head -n 1)
echo "✅ apksigner found at: $APKSIGNER_PATH"
echo "APKSIGNER_PATH=$APKSIGNER_PATH" >> $GITHUB_ENV
chmod +x "$APKSIGNER_PATH"
- name: Create zapstore.yaml metadata
- name: Build APK
run: |
cat > ./release/zapstore.yaml <<EOF
voca:
android:
name: "Voca"
description: "A simple text-to-speech app that lets you listen to text instead of reading it. Features include dark mode, text sharing from other apps, and customizable TTS settings."
repository: "https://git.nostrdev.com/voca/voca"
license: "MIT"
categories:
- "utilities"
- "accessibility"
author: "Voca"
tags:
- "text-to-speech"
- "reader"
- "accessibility"
icons:
- url: "https://git.nostrdev.com/voca/voca/raw/branch/main/voca_logo.jpg"
type: "image/jpeg"
EOF
./gradlew assembleRelease --build-cache --parallel --max-workers=4
mkdir -p ./release
cp app/build/outputs/apk/release/app-release.apk ./release/
echo "# Release $VERSION" > ./release/release_notes.md
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '20.8.1'
- name: Install semantic-release
run: |
npm init -y
npm install --save-dev \
semantic-release \
@semantic-release/changelog \
@semantic-release/git \
@semantic-release/exec
- name: Set up Git for semantic-release
env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
git config --global user.name "CI Bot"
git config --global user.email "ci@nostrdev.com"
git remote set-url origin "https://$RELEASE_TOKEN@git.nostrdev.com/voca/voca.git"
- name: Run semantic-release dry run
id: check_release
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITEA_URL: https://git.nostrdev.com/voca/voca
GIT_AUTHOR_NAME: "CI Bot"
GIT_AUTHOR_EMAIL: "ci@nostrdev.com"
GIT_COMMITTER_NAME: "CI Bot"
GIT_COMMITTER_EMAIL: "ci@nostrdev.com"
GIT_ASKPASS: echo
GIT_TERMINAL_PROMPT: 0
GIT_CREDENTIALS: https://${{ secrets.RELEASE_TOKEN }}@git.nostrdev.com/voca/voca.git
run: |
echo "🔍 Checking for version changes since last tag..."
OUTPUT=$(npx semantic-release --debug --dry-run || true)
echo "$OUTPUT"
echo "$OUTPUT" | grep -q "There are no relevant changes" && echo "released=false" >> $GITHUB_OUTPUT || echo "released=true" >> $GITHUB_OUTPUT
- name: Perform semantic release
if: ${{ steps.check_release.outputs.released == 'true' }}
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITEA_URL: https://git.nostrdev.com/voca/voca
GIT_AUTHOR_NAME: "CI Bot"
GIT_AUTHOR_EMAIL: "ci@nostrdev.com"
GIT_COMMITTER_NAME: "CI Bot"
GIT_COMMITTER_EMAIL: "ci@nostrdev.com"
GIT_ASKPASS: echo
GIT_TERMINAL_PROMPT: 0
GIT_CREDENTIALS: https://${{ secrets.RELEASE_TOKEN }}@git.nostrdev.com/voca/voca.git
run: npx semantic-release --debug
- name: Extract released version
id: get_version
if: ${{ steps.check_release.outputs.released == 'true' }}
run: |
VERSION=$(git describe --tags --abbrev=0)
echo "Extracted version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Rename APK with version
# Run even if no release happened to get a consistently named APK artifact
# Use a default name if no version was extracted
run: |
VERSION=${{ steps.get_version.outputs.version || 'dev' }}
echo "Using version '$VERSION' for APK name"
mv ./release/app-release.apk ./release/voca-${VERSION}.apk
- name: Validate APK file
run: |
# Use the same logic to find the potentially renamed APK
APK_PATH="./release/app-release.apk"
if [ -f ./release/voca-${{ steps.get_version.outputs.version || 'dev' }}.apk ]; then
APK_PATH="./release/voca-${{ steps.get_version.outputs.version || 'dev' }}.apk"
fi
echo "Validating APK at: $APK_PATH"
test -f "$APK_PATH" || { echo "❌ APK file missing at $APK_PATH"; exit 1; }
SIZE=$(stat -c%s "$APK_PATH")
echo "APK size: $SIZE bytes"
if [ "$SIZE" -lt 1000 ]; then
echo "❌ APK is too small or corrupted"
exit 1
fi
unzip -l "$APK_PATH" | grep AndroidManifest.xml || {
echo "❌ APK appears invalid or not a valid Android archive"
exit 1
}
echo "✅ APK is valid"
- name: Upload APK as artifact
# Always upload the artifact so it's available for manual Zapstore run
uses: actions/upload-artifact@v3
with:
name: apk-${{ steps.get_version.outputs.version || github.sha }}
path: ./release/voca-*.apk
publish_gitea_release:
name: Publish Release to Gitea
needs: build_and_release
if: success() && needs.build_and_release.outputs.released == 'true'
runs-on: ubuntu-latest
steps:
- name: Install GitHub CLI
run: |
sudo apt-get update
sudo apt-get install -y curl unzip
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \
sudo gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | \
sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update
sudo apt install -y gh
- name: Download APK artifact
uses: actions/download-artifact@v3
with:
# Use the versioned artifact name
name: apk-${{ needs.build_and_release.outputs.version }}
path: ./release
- name: List downloaded files
run: ls -la ./release
- name: Create Gitea Release and Upload APK (using Gitea API)
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_VERSION: ${{ needs.build_and_release.outputs.version }}
REPO_OWNER: ${{ github.repository_owner }} # Assumes Gitea owner is same as repo context
REPO_NAME: ${{ github.event.repository.name }} # Assumes Gitea repo name is same as repo context
run: |
APK_FILE=$(find ./release -name 'voca-*.apk' -print -quit)
if [ -z "$APK_FILE" ]; then
echo "❌ APK file not found in ./release directory!"
exit 1
fi
echo "✅ Found APK: $APK_FILE"
# Add debug echo for the version
echo "✅ Version received from build job: '${RELEASE_VERSION}'"
echo "Version to release: ${RELEASE_VERSION}"
# Construct the API URL for creating the release
GITEA_API_URL="https://git.nostrdev.com/api/v1"
RELEASE_API_URL="$GITEA_API_URL/repos/$REPO_OWNER/$REPO_NAME/releases"
# Try to fetch the CHANGELOG.md from the repository
# Extract release notes from CHANGELOG.md
echo "Extracting release notes from CHANGELOG.md..."
if [ -f "docs/CHANGELOG.md" ]; then
# Extract content between the version heading for current version and the next version heading
VERSION_PATTERN=$(echo "$VERSION" | sed 's/\./\\./g')
awk -v ver="# \\[${VERSION_PATTERN}\\]" -v started=0 '
VERSION_PATTERN=$(echo "${RELEASE_VERSION}" | sed 's/\./\\./g')
RELEASE_NOTES=$(awk -v ver="# \\[${VERSION_PATTERN}\\]" -v started=0 '
$0 ~ ver {
started = 1;
next;
@ -139,68 +305,70 @@ jobs:
exit;
}
}
started == 1 { print }
' docs/CHANGELOG.md >> ./release/release_notes.md
started == 1 { notes = notes $0 "\n" }
END { print notes }
' docs/CHANGELOG.md)
# If extraction failed or produced empty result, use a fallback
if [ -z "${RELEASE_NOTES}" ]; then
RELEASE_NOTES="Automated release for ${RELEASE_VERSION}. See CHANGELOG.md for details."
fi
else
echo "See repository for full changelog." >> ./release/release_notes.md
RELEASE_NOTES="Automated release for ${RELEASE_VERSION}. See CHANGELOG.md for details."
fi
# Display the release notes that will be used
echo "Release notes for Zapstore:"
cat ./release/release_notes.md
echo "Using release notes: ${RELEASE_NOTES}"
- name: Validate APK file
run: |
APK_NAME="voca-${VERSION}.apk"
APK_PATH="./release/${APK_NAME}"
test -f "$APK_PATH" || { echo "❌ APK file not found: $APK_PATH"; exit 1; }
SIZE=$(stat -c%s "$APK_PATH")
echo "APK size: $SIZE bytes"
if [ "$SIZE" -lt 1000 ]; then
echo "❌ APK is too small or corrupted"
exit 1
fi
unzip -l "$APK_PATH" | grep AndroidManifest.xml || {
echo "❌ APK appears invalid or not a valid Android archive"
exit 1
echo "Creating Gitea release for tag ${RELEASE_VERSION}..."
# Create the release via Gitea API - Using the version directly as the tag name
CREATE_RESPONSE=$(curl -s -X POST "$RELEASE_API_URL" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d @- << EOF
{
"tag_name": "${RELEASE_VERSION}",
"target_commitish": "${{ github.sha }}",
"name": "Release ${RELEASE_VERSION}",
"body": "$RELEASE_NOTES",
"draft": false,
"prerelease": false
}
EOF
)
- name: Publish to Zapstore
env:
NSEC: ${{ secrets.NSEC }}
APKSIGNER_PATH: ${{ env.APKSIGNER_PATH }}
run: |
cd ./release
echo "Create Response: $CREATE_RESPONSE"
curl -L -o voca_logo.jpg "https://git.nostrdev.com/voca/voca/raw/branch/main/voca_logo.jpg"
# Extract the ID of the created release
RELEASE_ID=$(echo "$CREATE_RESPONSE" | jq -r '.id')
echo "Publishing to Zapstore using apksigner at $APKSIGNER_PATH..."
# Create a script to pipe 'yes' to handle any prompts
APKSIGNER_PATH="$APKSIGNER_PATH" zapstore publish voca \
--daemon-mode \
--overwrite-app \
--overwrite-release \
--icon voca_logo.jpg \
-n release_notes.md \
-a "voca-${VERSION}.apk" \
-v "$VERSION"
# Check the exit code
RESULT=$?
if [ $RESULT -ne 0 ]; then
echo "❌ Zapstore publish failed with exit code $RESULT"
if [ "$RELEASE_ID" == "null" ] || [ -z "$RELEASE_ID" ]; then
echo "❌ Failed to create Gitea release or extract release ID."
echo "API URL: $RELEASE_API_URL"
exit 1
fi
echo "✅ Gitea release created with ID: $RELEASE_ID"
# Construct the URL for uploading assets
ASSET_UPLOAD_URL="$GITEA_API_URL/repos/$REPO_OWNER/$REPO_NAME/releases/$RELEASE_ID/assets?name=$(basename "$APK_FILE")"
echo "Uploading asset $APK_FILE to release ID $RELEASE_ID..."
# Upload the APK asset
UPLOAD_RESPONSE=$(curl -s -X POST "$ASSET_UPLOAD_URL" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$APK_FILE"
)
echo "Upload Response: $UPLOAD_RESPONSE"
# Very basic check for upload success (Gitea API might return more details)
if echo "$UPLOAD_RESPONSE" | jq -e '.id' > /dev/null; then
echo "✅ Asset uploaded successfully."
else
echo "✅ Successfully published to Zapstore!"
echo "❌ Failed to upload asset."
exit 1
fi
- name: ✅ Notify success
if: ${{ success() }}
run: |
echo "🎉 Voca v${VERSION} published to Zapstore!"
echo "🔗 https://zapstore.dev/app/voca"

@ -1 +1,4 @@
# Voca v0.0.6\n\n## Changes in v0.0.6:\n- Improved text-to-speech functionality\n- Bug fixes and performance improvements
# Voca v0.0.6\n\n## Changes in v0.1.1:\n- Improved text-to-speech functionality\n- Bug fixes and performance improvements
1. Improved tts highlighting
2. Added mp3 creation
3. Improve state management and UI