ce4a034224
Consolidate and harden post-build cleanup by targeting the build's lib directory (lib_dir) instead of running broad finds across build_dir. Removes screenshots, PySide6 translations, specific Qt plugins and plugin files, and various Qt6 shared libraries (Web, Pdf, Qml, Quick, VirtualKeyboard, OpenGL) to reduce bundle size. Also removes common build tools/modules (setuptools, wheel, pkg_resources, _distutils_hack, curses, _pyrepl) and adds explicit logging and a warning if lib_dir is missing. Simplifies and centralizes cleanup logic for more predictable and efficient trimming of Linux artifacts.
594 lines
20 KiB
YAML
594 lines
20 KiB
YAML
name: Build Linux Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'Version name for the release (e.g., 1.0.0)'
|
|
required: true
|
|
type: string
|
|
workflow_call:
|
|
inputs:
|
|
version:
|
|
description: 'Version name for the release (e.g., 1.0.0)'
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
env:
|
|
PYTHON_VERSION: '3.13'
|
|
|
|
jobs:
|
|
build-linux:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Get version
|
|
id: get_version
|
|
shell: bash
|
|
run: |
|
|
version="${{ inputs.version || github.event.inputs.version }}"
|
|
echo "Extracted version: $version"
|
|
echo "VERSION=$version" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
|
|
- name: Cache Python dependencies
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
venv
|
|
~/.cache/pip
|
|
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-pip-
|
|
|
|
- name: Install system dependencies
|
|
shell: bash
|
|
run: |
|
|
sudo apt-get update -y
|
|
# Tools commonly required by cx_Freeze on Linux packaging
|
|
sudo apt-get install -y --no-install-recommends \
|
|
rpm alien fakeroot patchelf desktop-file-utils file xz-utils zsync
|
|
|
|
- name: Create virtual environment and install dependencies
|
|
shell: bash
|
|
run: |
|
|
python -m venv venv
|
|
source venv/bin/activate
|
|
python -m pip install --upgrade pip
|
|
pip install --no-cache-dir .
|
|
pip install --no-cache-dir cx_Freeze
|
|
|
|
- name: Prepare build variables
|
|
shell: bash
|
|
run: |
|
|
version="${{ steps.get_version.outputs.VERSION }}"
|
|
echo "VERSION=$version" >> "$GITHUB_ENV"
|
|
arch="$(uname -m)" # x86_64 or aarch64
|
|
echo "ARCH=$arch" >> "$GITHUB_ENV"
|
|
echo "Prepared build variables for version: $version on $(uname -a)"
|
|
|
|
- name: Create cx_Freeze setup script (Linux)
|
|
shell: bash
|
|
run: |
|
|
# Create entry point script
|
|
cat > ytsage_entry.py <<'ENTRY'
|
|
from ytsage.main import main
|
|
if __name__ == "__main__":
|
|
main()
|
|
ENTRY
|
|
|
|
cat > setup_cxfreeze.py <<'PY'
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from cx_Freeze import setup, Executable
|
|
|
|
version = os.environ.get("VERSION", "0.0.0")
|
|
|
|
# Prepare include_files list
|
|
include_files_list = [
|
|
("ytsage/assets/Icon", "lib/assets/Icon"),
|
|
("ytsage/assets/sound", "lib/assets/sound"),
|
|
("ytsage/languages", "lib/languages"),
|
|
("branding/icons", "lib/assets/branding/icons"),
|
|
("ytsage.desktop", "share/applications/ytsage.desktop"),
|
|
("branding/icons/icon.png", "share/pixmaps/ytsage.png"),
|
|
]
|
|
|
|
build_exe_options = dict(
|
|
optimize=2,
|
|
silent=True,
|
|
packages=[
|
|
"ytsage",
|
|
"PySide6.QtCore",
|
|
"PySide6.QtGui",
|
|
"PySide6.QtWidgets",
|
|
"PySide6.QtMultimedia",
|
|
"PySide6.QtNetwork",
|
|
"requests",
|
|
"PIL",
|
|
"packaging",
|
|
"markdown",
|
|
"loguru",
|
|
"setuptools",
|
|
],
|
|
zip_include_packages=[
|
|
"PySide6",
|
|
"shiboken6",
|
|
"requests",
|
|
"PIL",
|
|
"packaging",
|
|
],
|
|
excludes=[
|
|
"PySide6.QtBluetooth",
|
|
"PySide6.QtOpenGL",
|
|
"PySide6.QtPrintSupport",
|
|
"PySide6.QtSvg",
|
|
"PySide6.QtTest",
|
|
"PySide6.QtXml",
|
|
"PySide6.QtSql",
|
|
"PySide6.QtHelp",
|
|
"PySide6.QtQml",
|
|
"PySide6.QtQuick",
|
|
"PySide6.QtWebEngineCore",
|
|
"PIL.ImageDraw",
|
|
"PIL.ImageFont",
|
|
"numpy",
|
|
"scipy",
|
|
"wx",
|
|
"pandas",
|
|
"tkinter",
|
|
"yt_dlp",
|
|
"unittest",
|
|
"test",
|
|
"tests",
|
|
"pydoc",
|
|
"doctest",
|
|
"email",
|
|
],
|
|
include_files=include_files_list,
|
|
# Bundle all dependencies - avoid system library references
|
|
bin_includes=[],
|
|
bin_excludes=[],
|
|
# Important: include system libs to avoid external dependencies
|
|
replace_paths=[("*", "")],
|
|
)
|
|
|
|
executables = [
|
|
Executable(
|
|
script="ytsage_entry.py",
|
|
target_name="ytsage",
|
|
icon="branding/icons/icon.png",
|
|
)
|
|
]
|
|
|
|
setup(
|
|
name="ytsage",
|
|
version=version,
|
|
description="YTSage",
|
|
options={
|
|
"build_exe": build_exe_options,
|
|
# AppImage packaging
|
|
"bdist_appimage": {
|
|
# If ends with .AppImage, use verbatim file name
|
|
"target_name": f"ytsage-v{version}.AppImage",
|
|
},
|
|
},
|
|
executables=executables,
|
|
)
|
|
PY
|
|
|
|
- name: Create desktop entry
|
|
shell: bash
|
|
run: |
|
|
cat > ytsage.desktop <<'DESKTOP'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=YTSage
|
|
Comment=YouTube downloader
|
|
Exec=/usr/bin/ytsage %U
|
|
Icon=ytsage
|
|
Terminal=false
|
|
Categories=AudioVideo;Network;Utility;
|
|
StartupWMClass=YTSage
|
|
DESKTOP
|
|
|
|
- name: Build AppImage and RPM
|
|
shell: bash
|
|
run: |
|
|
set -e
|
|
source venv/bin/activate
|
|
|
|
# Ensure a clean build and build_exe first with -OO
|
|
python -OO setup_cxfreeze.py build_exe
|
|
|
|
# Clean unnecessary files from build directory before packaging
|
|
build_dir=$(ls -d build/exe.* 2>/dev/null | head -n1 || true)
|
|
|
|
if [ -n "$build_dir" ]; then
|
|
echo "Trimming files in $build_dir..."
|
|
lib_dir="$build_dir/lib"
|
|
|
|
if [ -d "$lib_dir" ]; then
|
|
# 1. Remove screenshots
|
|
if [ -d "$lib_dir/assets/branding/screenshots" ]; then
|
|
rm -rf "$lib_dir/assets/branding/screenshots"
|
|
echo "Removed screenshots folder"
|
|
fi
|
|
|
|
# 2. Remove unused Qt translations
|
|
if [ -d "$lib_dir/PySide6/translations" ]; then
|
|
rm -rf "$lib_dir/PySide6/translations"
|
|
echo "Removed Qt translations"
|
|
fi
|
|
|
|
# 3. Remove unused Qt plugins
|
|
plugins_dir="$lib_dir/PySide6/plugins"
|
|
if [ -d "$plugins_dir" ]; then
|
|
for plugin in designer pdf svg sql help qml quick webengine bluetooth opengl printsupport test xml; do
|
|
if [ -d "$plugins_dir/$plugin" ]; then
|
|
rm -rf "$plugins_dir/$plugin"
|
|
echo "Removed plugin: $plugin"
|
|
fi
|
|
done
|
|
# Specific cleanups (input contexts, imageformats)
|
|
rm -rf "$plugins_dir/platforminputcontexts"
|
|
rm -f "$plugins_dir/imageformats/libqpdf.so" "$plugins_dir/imageformats/libqsvg.so"
|
|
rm -f "$plugins_dir/iconengines/libqsvgicon.so"
|
|
fi
|
|
|
|
# 4. Remove bloat shared libraries (Recursively finds in lib/ and lib/PySide6/)
|
|
# Matches libQt6Qml.so.6, Qt6Qml.abi3.so, etc.
|
|
echo "Removing bloat Qt libraries..."
|
|
find "$lib_dir" -name "*Qt6Web*" -delete
|
|
find "$lib_dir" -name "*Qt6Pdf*" -delete
|
|
find "$lib_dir" -name "*Qt6Qml*" -delete
|
|
find "$lib_dir" -name "*Qt6Quick*" -delete
|
|
find "$lib_dir" -name "*Qt6VirtualKeyboard*" -delete
|
|
find "$lib_dir" -name "*Qt6OpenGL*" -delete
|
|
|
|
# 5. Remove build tools and unused python modules
|
|
for tool in setuptools wheel pkg_resources _distutils_hack curses _pyrepl; do
|
|
rm -rf "$lib_dir/$tool"
|
|
echo "Removed build tool/module: $tool"
|
|
done
|
|
else
|
|
echo "Warning: lib directory not found inside build_dir"
|
|
fi
|
|
else
|
|
echo "Warning: Build directory not found, cannot trim files"
|
|
fi
|
|
|
|
# Build AppImage
|
|
python setup_cxfreeze.py bdist_appimage
|
|
|
|
# Build RPM manually with proper spec file
|
|
version="${{ steps.get_version.outputs.VERSION }}"
|
|
arch_uname="${ARCH}"
|
|
workspace_root="$(pwd)"
|
|
build_dir_abs="${workspace_root}/${build_dir}"
|
|
|
|
# Create custom RPM spec file that doesn't auto-detect dependencies
|
|
mkdir -p build/rpm/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
|
|
|
|
cat > build/rpm/SPECS/ytsage.spec <<SPEC
|
|
%global __requires_exclude_from ^/opt/ytsage/.*$
|
|
%global __provides_exclude_from ^/opt/ytsage/.*$
|
|
%define _build_id_links none
|
|
%undefine _missing_build_ids_terminate_build
|
|
|
|
Name: ytsage
|
|
Version: ${version//-/.}
|
|
Release: 1%{?dist}
|
|
Summary: YTSage - A modern YouTube downloader
|
|
License: MIT
|
|
URL: https://github.com/oop7/YTSage
|
|
AutoReqProv: no
|
|
|
|
%description
|
|
YTSage is a modern YouTube downloader with a PySide6 interface.
|
|
Download videos, extract audio, fetch subtitles, and more.
|
|
All dependencies are bundled within the package.
|
|
|
|
%install
|
|
rm -rf %{buildroot}
|
|
mkdir -p %{buildroot}/opt/ytsage
|
|
mkdir -p %{buildroot}/usr/bin
|
|
mkdir -p %{buildroot}/usr/share/applications
|
|
mkdir -p %{buildroot}/usr/share/pixmaps
|
|
|
|
# Copy application files
|
|
cp -a ${build_dir_abs}/* %{buildroot}/opt/ytsage/
|
|
|
|
# Create wrapper script
|
|
cat > %{buildroot}/usr/bin/ytsage <<'WRAPPER'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
APPDIR="/opt/ytsage"
|
|
cd "\$APPDIR"
|
|
exec "\$APPDIR/ytsage" "\$@"
|
|
WRAPPER
|
|
chmod 0755 %{buildroot}/usr/bin/ytsage
|
|
|
|
# Install desktop file and icon
|
|
install -m 0644 ${workspace_root}/ytsage.desktop %{buildroot}/usr/share/applications/ytsage.desktop
|
|
install -m 0644 ${workspace_root}/branding/icons/icon.png %{buildroot}/usr/share/pixmaps/ytsage.png
|
|
|
|
%files
|
|
/opt/ytsage
|
|
/usr/bin/ytsage
|
|
/usr/share/applications/ytsage.desktop
|
|
/usr/share/pixmaps/ytsage.png
|
|
|
|
%post
|
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
update-desktop-database -q /usr/share/applications || true
|
|
fi
|
|
|
|
%postun
|
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
update-desktop-database -q /usr/share/applications || true
|
|
fi
|
|
|
|
%changelog
|
|
* $(date +'%a %b %d %Y') YTSage Maintainers <noreply@example.com> - ${version//-/.}-1
|
|
- Release ${version}
|
|
SPEC
|
|
|
|
# Build the RPM
|
|
rpmbuild -bb build/rpm/SPECS/ytsage.spec \
|
|
--define "_topdir $(pwd)/build/rpm" \
|
|
--buildroot "$(pwd)/build/rpm/BUILDROOT"
|
|
|
|
# Copy RPM to dist
|
|
mkdir -p dist
|
|
find build/rpm/RPMS -name "*.rpm" -exec cp {} dist/ \;
|
|
|
|
echo "Post-build directory listing:"
|
|
echo "-- dist --"; ls -lah dist || true
|
|
echo "-- build --"; ls -lah build || true
|
|
|
|
- name: Build native DEB package
|
|
shell: bash
|
|
run: |
|
|
set -e
|
|
version="${{ steps.get_version.outputs.VERSION }}"
|
|
arch_uname="${ARCH}"
|
|
# Map uname -m to Debian arch names
|
|
case "$arch_uname" in
|
|
x86_64) deb_arch=amd64 ;;
|
|
aarch64) deb_arch=arm64 ;;
|
|
*) deb_arch="$arch_uname" ;;
|
|
esac
|
|
|
|
# Locate build output
|
|
build_dir=$(ls -d build/exe.* 2>/dev/null | head -n1)
|
|
if [ -z "$build_dir" ]; then
|
|
echo "Error: build/exe.* directory not found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Staging root
|
|
pkgroot=deb_pkg
|
|
rm -rf "$pkgroot"
|
|
mkdir -p "$pkgroot/DEBIAN" \
|
|
"$pkgroot/usr/bin" \
|
|
"$pkgroot/usr/share/applications" \
|
|
"$pkgroot/usr/share/pixmaps" \
|
|
"$pkgroot/opt/ytsage"
|
|
|
|
# Install application payload under /opt/ytsage
|
|
cp -a "$build_dir"/* "$pkgroot/opt/ytsage/"
|
|
|
|
# Wrapper to ensure correct working directory
|
|
cat > "$pkgroot/usr/bin/ytsage" <<'WRAP'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
APPDIR="/opt/ytsage"
|
|
cd "$APPDIR"
|
|
exec "$APPDIR/ytsage" "$@"
|
|
WRAP
|
|
chmod 0755 "$pkgroot/usr/bin/ytsage"
|
|
|
|
# Desktop file and icon
|
|
install -m 0644 ytsage.desktop "$pkgroot/usr/share/applications/ytsage.desktop"
|
|
install -m 0644 branding/icons/icon.png "$pkgroot/usr/share/pixmaps/ytsage.png"
|
|
|
|
# Control file
|
|
cat > "$pkgroot/DEBIAN/control" <<CONTROL
|
|
Package: ytsage
|
|
Version: ${version}
|
|
Section: utils
|
|
Priority: optional
|
|
Architecture: ${deb_arch}
|
|
Maintainer: YTSage Maintainers <noreply@example.com>
|
|
Homepage: https://github.com/oop7/YTSage
|
|
Description: YTSage - A modern YouTube downloader with a PySide6 interface
|
|
Download videos, extract audio, fetch subtitles, and more.
|
|
CONTROL
|
|
|
|
# Post-install script to refresh desktop/menu caches (best-effort)
|
|
cat > "$pkgroot/DEBIAN/postinst" <<'POSTINST'
|
|
#!/bin/sh
|
|
set -e
|
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
update-desktop-database -q || true
|
|
fi
|
|
if command -v gtk-update-icon-cache >/dev/null 2>&1; then
|
|
gtk-update-icon-cache -q /usr/share/icons/hicolor || true
|
|
fi
|
|
exit 0
|
|
POSTINST
|
|
chmod 0755 "$pkgroot/DEBIAN/postinst"
|
|
|
|
# Triggers so Debian updates caches
|
|
cat > "$pkgroot/DEBIAN/triggers" <<'TRIGGERS'
|
|
interest-noawait /usr/share/applications
|
|
interest-noawait /usr/share/icons/hicolor
|
|
TRIGGERS
|
|
|
|
# Build the deb
|
|
deb_out="YTSage-v${version}-${deb_arch}.deb"
|
|
dpkg-deb --build "$pkgroot" "$deb_out"
|
|
|
|
mkdir -p artifacts
|
|
mv "$deb_out" artifacts/
|
|
echo "Built native DEB: artifacts/$deb_out"
|
|
|
|
- name: Build Flatpak Bundle
|
|
shell: bash
|
|
run: |
|
|
version="${{ steps.get_version.outputs.VERSION }}"
|
|
|
|
# Install flatpak-builder
|
|
sudo apt-get install -y flatpak-builder
|
|
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
|
|
|
# Define manifest ID
|
|
APP_ID="io.github.oop7.YTSage"
|
|
|
|
# Create a specific desktop file for Flatpak
|
|
# - Exec must be 'ytsage' (in path), not /usr/bin/ytsage
|
|
# - Icon must match the App ID (io.github.oop7.YTSage)
|
|
cat > flatpak.desktop <<EOF
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=YTSage
|
|
Comment=YouTube downloader
|
|
Exec=ytsage
|
|
Icon=$APP_ID
|
|
Terminal=false
|
|
Categories=AudioVideo;Network;Utility;
|
|
StartupWMClass=YTSage
|
|
EOF
|
|
|
|
# Create manifest manually
|
|
cat > ytsage_flatpak.json <<EOF
|
|
{
|
|
"app-id": "$APP_ID",
|
|
"runtime": "org.kde.Platform",
|
|
"runtime-version": "6.10",
|
|
"sdk": "org.kde.Sdk",
|
|
"command": "ytsage",
|
|
"finish-args": [
|
|
"--share=ipc",
|
|
"--socket=x11",
|
|
"--socket=wayland",
|
|
"--socket=pulseaudio",
|
|
"--device=dri",
|
|
"--share=network",
|
|
"--filesystem=host",
|
|
"--env=YTDLP_APP_BIN_PATH=/var/data/yt-dlp",
|
|
"--env=DENO_APP_BIN_PATH=/var/data/deno"
|
|
],
|
|
"modules": [
|
|
{
|
|
"name": "ytsage",
|
|
"buildsystem": "simple",
|
|
"build-commands": [
|
|
"mkdir -p /app/bin /app/share/ytsage",
|
|
"cp -r dist/ytsage-v${version}-*/* /app/share/ytsage/",
|
|
"ln -s /app/share/ytsage/ytsage /app/bin/ytsage",
|
|
"install -D flatpak.desktop /app/share/applications/$APP_ID.desktop",
|
|
"install -D branding/icons/icon.png /app/share/icons/hicolor/128x128/apps/$APP_ID.png"
|
|
],
|
|
"sources": [
|
|
{
|
|
"type": "dir",
|
|
"path": "."
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
EOF
|
|
|
|
# Build the Flatpak
|
|
# Note: In a real environment, building from source is preferred.
|
|
# Here we are "bundling" the already built binaries from the previous step (cx_Freeze) for simplicity in CI.
|
|
# This requires the 'dist/' folder to be populated by the previous 'Create cx_Freeze setup script' + build steps.
|
|
# Wait, the previous steps built into 'build/exe.linux-...' not 'dist/'. Let's find it.
|
|
|
|
build_dir=$(ls -d build/exe.* 2>/dev/null | head -n1)
|
|
if [ -z "$build_dir" ]; then
|
|
echo "Error: build/exe.* directory not found for Flatpak build" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Move build dir to a fixed location for the manifest source to catch
|
|
mkdir -p dist/ytsage-v${version}-flatpak
|
|
cp -r "$build_dir"/* "dist/ytsage-v${version}-flatpak/"
|
|
|
|
# Install runtime/sdk
|
|
flatpak install -y --user flathub org.kde.Platform//6.10 org.kde.Sdk//6.10
|
|
|
|
# Build
|
|
flatpak-builder --user --install-deps-from=flathub --repo=repo --force-clean build-flatpak ytsage_flatpak.json
|
|
|
|
# Bundle
|
|
flatpak build-bundle repo artifacts/YTSage-v${version}-x86_64.flatpak $APP_ID
|
|
echo "Built Flatpak: artifacts/YTSage-v${version}-x86_64.flatpak"
|
|
|
|
- name: Package and prepare release artifacts (.AppImage, .rpm, .deb)
|
|
shell: bash
|
|
run: |
|
|
version="${{ steps.get_version.outputs.VERSION }}"
|
|
mkdir -p artifacts
|
|
|
|
# Copy AppImage
|
|
appimage_src=$(ls dist/ytsage-v*.AppImage 2>/dev/null | head -n1 || true)
|
|
if [ -n "$appimage_src" ]; then
|
|
cp "$appimage_src" "artifacts/YTSage-v${version}-${ARCH}.AppImage"
|
|
echo "Copied AppImage: $appimage_src -> artifacts/YTSage-v${version}-${ARCH}.AppImage"
|
|
else
|
|
echo "Warning: No .AppImage found in dist/"
|
|
fi
|
|
|
|
# Copy RPM (normalize name)
|
|
rpm_src=$(ls dist/ytsage-*.rpm 2>/dev/null | head -n1 || true)
|
|
if [ -n "$rpm_src" ]; then
|
|
cp "$rpm_src" "artifacts/YTSage-v${version}-${ARCH}.rpm"
|
|
echo "Copied RPM: $rpm_src -> artifacts/YTSage-v${version}-${ARCH}.rpm"
|
|
else
|
|
echo "Warning: No .rpm found in dist/"
|
|
fi
|
|
|
|
# DEB is already staged in artifacts by the native packaging step
|
|
ls -1 artifacts/*.deb 2>/dev/null || echo "Warning: No .deb found in artifacts/"
|
|
|
|
# Check for Flatpak
|
|
ls -1 artifacts/*.flatpak 2>/dev/null || echo "Warning: No .flatpak found in artifacts/"
|
|
|
|
echo "Final artifacts:"
|
|
ls -lh artifacts || true
|
|
|
|
- name: Create/Update draft release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: v${{ steps.get_version.outputs.VERSION }}
|
|
name: YTSage v${{ steps.get_version.outputs.VERSION }}
|
|
draft: true
|
|
prerelease: false
|
|
append_body: true
|
|
fail_on_unmatched_files: false
|
|
body: |
|
|
# YTSage v${{ steps.get_version.outputs.VERSION }}
|
|
|
|
**Release Date**: ${{ github.event.head_commit.timestamp }}
|
|
files: |
|
|
artifacts/*
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|