#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: ./scripts/release/release-prepare-assets.sh --version v0.2.0-alpha.1 [options] Options: --windows-zip Windows x64 ZIP artifact path --windows-portable-exe Windows onefile portable EXE artifact path --windows-installer-exe Windows installer EXE artifact path --android-apk Android universal APK artifact path --zip Backward-compatible alias for --windows-zip --channel Release channel. Default: alpha --notes Existing Korean release notes file --screenshots Directory containing *.png/jpg screenshots --force Overwrite an existing release folder Environment: DOWNLOAD_BASE_URL Defaults to https://download-vstalk.phy.kr EOF } version="" channel="alpha" windows_zip="" windows_portable_exe="" windows_installer_exe="" android_apk="" notes_path="" screenshots_dir="" force="false" while [[ $# -gt 0 ]]; do case "$1" in --version) version="${2:-}" shift 2 ;; --channel) channel="${2:-}" shift 2 ;; --windows-zip|--zip) windows_zip="${2:-}" shift 2 ;; --windows-portable-exe) windows_portable_exe="${2:-}" shift 2 ;; --windows-installer-exe) windows_installer_exe="${2:-}" shift 2 ;; --android-apk) android_apk="${2:-}" shift 2 ;; --notes) notes_path="${2:-}" shift 2 ;; --screenshots) screenshots_dir="${2:-}" shift 2 ;; --force) force="true" shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 1 ;; esac done if [[ -z "$version" ]]; then usage >&2 exit 1 fi if [[ -z "$windows_zip" && -z "$windows_portable_exe" && -z "$windows_installer_exe" && -z "$android_apk" ]]; then echo "At least one artifact must be provided for Windows or Android." >&2 usage >&2 exit 1 fi if [[ -n "$windows_zip" && ! -f "$windows_zip" ]]; then echo "Windows ZIP artifact not found: $windows_zip" >&2 exit 1 fi if [[ -n "$windows_portable_exe" && ! -f "$windows_portable_exe" ]]; then echo "Windows portable EXE artifact not found: $windows_portable_exe" >&2 exit 1 fi if [[ -n "$windows_installer_exe" && ! -f "$windows_installer_exe" ]]; then echo "Windows installer EXE artifact not found: $windows_installer_exe" >&2 exit 1 fi if [[ -n "$android_apk" && ! -f "$android_apk" ]]; then echo "Android APK artifact not found: $android_apk" >&2 exit 1 fi repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" release_root="$repo_root/release-assets/releases/$version" latest_root="$repo_root/release-assets/latest" download_root="$repo_root/release-assets/root" template_path="$repo_root/release-assets/templates/RELEASE_NOTES.ko.md" download_base_url="${DOWNLOAD_BASE_URL:-https://download-vstalk.phy.kr}" release_base_url="${RELEASE_BASE_URL:-}" published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" derive_release_url() { if [[ -n "$release_base_url" ]]; then printf '%s/releases/tag/%s' "${release_base_url%/}" "$version" return 0 fi local origin_url origin_url="$(git -C "$repo_root" remote get-url origin 2>/dev/null || true)" if [[ -z "$origin_url" ]]; then return 0 fi if [[ "$origin_url" =~ ^https?:// ]]; then printf '%s/releases/tag/%s' "${origin_url%.git}" "$version" return 0 fi if [[ "$origin_url" =~ ^git@([^:]+):(.+)\.git$ ]]; then printf 'https://%s/%s/releases/tag/%s' "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "$version" return 0 fi } release_url="$(derive_release_url)" if [[ -e "$release_root" && "$force" != "true" ]]; then echo "Release directory already exists: $release_root" >&2 echo "Use --force to replace it." >&2 exit 1 fi rm -rf "$release_root" "$latest_root" "$download_root" mkdir -p "$release_root/screenshots" "$latest_root/screenshots" "$download_root" if [[ -n "$notes_path" ]]; then cp "$notes_path" "$release_root/RELEASE_NOTES.ko.md" else sed \ -e "s/{{VERSION}}/$version/g" \ -e "s/{{CHANNEL}}/$channel/g" \ -e "s/{{PUBLISHED_AT}}/$published_at/g" \ "$template_path" > "$release_root/RELEASE_NOTES.ko.md" fi cp "$release_root/RELEASE_NOTES.ko.md" "$latest_root/RELEASE_NOTES.ko.md" append_screenshot_gallery() { local notes_file="$1" local screenshot_root_url="$2" if [[ -z "$screenshots_dir" ]]; then return 0 fi mapfile -t note_screenshots < <(find "$screenshots_dir" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | sort) if [[ ${#note_screenshots[@]} -eq 0 ]]; then return 0 fi { printf '\n## 최신 화면\n\n' printf '스크린샷은 릴리즈 Assets 대신 변경 노트 안에서 직접 확인할 수 있도록 정리합니다.\n\n' for screenshot in "${note_screenshots[@]}"; do name="$(basename "$screenshot")" label="${name%.*}" printf '### %s\n\n' "$label" printf '![%s](%s/%s)\n\n' "$label" "$screenshot_root_url" "$name" done } >> "$notes_file" } if [[ -n "$screenshots_dir" ]]; then while IFS= read -r screenshot; do cp "$screenshot" "$release_root/screenshots/$(basename "$screenshot")" cp "$screenshot" "$latest_root/screenshots/$(basename "$screenshot")" done < <(find "$screenshots_dir" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | sort) fi append_screenshot_gallery "$release_root/RELEASE_NOTES.ko.md" "$download_base_url/releases/$version/screenshots" cp "$release_root/RELEASE_NOTES.ko.md" "$latest_root/RELEASE_NOTES.ko.md" platform_count=0 platforms_json="" top_level_windows_alias="" release_hash_paths=() latest_hash_paths=() append_platform_json() { local body="$1" if (( platform_count > 0 )); then platforms_json+=$',\n' fi platforms_json+="$body" platform_count=$((platform_count + 1)) } join_json_lines() { local result="" local total=$# local index=0 local line="" for line in "$@"; do index=$((index + 1)) result+="$line" if (( index < total )); then result+=$',\n' fi done printf '%s' "$result" } write_platform_version_json() { local path="$1" local body="$2" cat > "$path" < SHA256SUMS.txt ) ( cd "$windows_latest_dir" sha256sum "${windows_latest_hash_items[@]}" > SHA256SUMS.txt ) windows_platform_body="$(join_json_lines "${windows_platform_lines[@]}")" append_platform_json "$(cat < SHA256SUMS.txt ) ( cd "$android_latest_dir" sha256sum "$android_latest_name" > SHA256SUMS.txt ) release_hash_paths+=("android/universal/$android_release_name") latest_hash_paths+=("android/$android_latest_name") android_platform_body="$(cat < 0 )); then ( cd "$release_root" sha256sum "${release_hash_paths[@]}" > SHA256SUMS.txt ) fi if (( ${#latest_hash_paths[@]} > 0 )); then ( cd "$latest_root" sha256sum "${latest_hash_paths[@]}" > SHA256SUMS.txt ) fi windows_landing_card="" if [[ -n "$windows_zip" || -n "$windows_portable_exe" || -n "$windows_installer_exe" ]]; then windows_landing_card="$(cat < Windows Latest Windows build Installer, onefile portable, ZIP, SHA256 EOF )" fi android_landing_card="" if [[ -n "$android_apk" ]]; then android_landing_card="$(cat < Android Latest Android build Universal APK and SHA256 checksum EOF )" fi windows_installer_card="" if [[ -n "$windows_installer_exe" ]]; then windows_installer_card="$(cat < Installer Windows installer 설치형 EXE EOF )" fi windows_portable_card="" if [[ -n "$windows_portable_exe" ]]; then windows_portable_card="$(cat < Portable Onefile executable 압축 해제 없이 바로 실행 EOF )" fi windows_zip_card="" if [[ -n "$windows_zip" ]]; then windows_zip_card="$(cat < Archive Extracted bundle ZIP 폴더 배포본 압축 파일 EOF )" fi if [[ -d "${latest_root}/windows" ]]; then cat > "${latest_root}/windows/index.html" < KoTalk for Windows

KoTalk for Windows

설치형과 onefile portable, 압축본을 같은 기준선으로 제공합니다.

$windows_installer_card $windows_portable_card $windows_zip_card Integrity SHA256SUMS 체크섬 확인

version.json

EOF fi if [[ -d "${latest_root}/android" ]]; then cat > "${latest_root}/android/index.html" < KoTalk for Android

KoTalk for Android

KoTalk Android shell APK와 버전 메타데이터를 같은 기준선으로 제공합니다.

version.json

EOF fi cat > "$download_root/index.html" < KoTalk Downloads

KoTalk Downloads

KoTalk의 최신 배포 파일과 버전 메타데이터를 제공하는 공식 다운로드 표면입니다.

Version
$version

Channel
$channel

Published
$published_at

Latest notes
RELEASE_NOTES.ko.md

EOF mapfile -t screenshot_files < <(find "$release_root/screenshots" -maxdepth 1 -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) | sort) screenshots_json="[]" if [[ ${#screenshot_files[@]} -gt 0 ]]; then screenshots_json=$( for idx in "${!screenshot_files[@]}"; do name="$(basename "${screenshot_files[$idx]}")" printf ' "%s/releases/%s/screenshots/%s"' "$download_base_url" "$version" "$name" if (( idx < ${#screenshot_files[@]} - 1 )); then printf ',\n' else printf '\n' fi done ) screenshots_json="[ $screenshots_json ]" fi cat > "$release_root/version.json" <