mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
* feat(embedded-mpv): Linux frame-copy helper via headless EGL Port the frame-copy engine's native layer to Linux (PORTING.md items 1-4): - frame_helper_gl.h: platform GlContext abstraction. macOS keeps the CGL path (moved verbatim); Linux acquires an EGL display in order surfaceless-Mesa -> default display -> GBM render node, binds a 3.2 core desktop-GL context surfaceless (1x1 pbuffer fallback), and hands mpv eglGetProcAddress. The helper's own GL calls link against glvnd libOpenGL, so no display server is required. - frame_shm.h: portable frame_shm_now_ns() (CLOCK_MONOTONIC) shared by the helper and the reader addon, replacing the macOS-only clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW); producer and consumer stay on the same clock. - embedded_mpv_frame_reader.c: real implementation now also on __linux__ (the code was already POSIX apart from the clock call). - binding.gyp: OS==linux executable branch for iptvnator_mpv_helper linking system libmpv (-lmpv) + EGL/OpenGL/gbm, with rpaths for $ORIGIN/lib and the build-time library dir. The in-process addon still does not link libmpv - the ban only binds in-process, the helper is out of process. - build-embedded-mpv.js: system-dev fallback on Linux (LIBMPV_INCLUDE_DIR or /usr/include) so a distro libmpv-dev install builds without staging a vendored runtime; a pre-set LINUX_NATIVE_LIBRARY_DIR now wins over the vendored lib dir. Verified on Ubuntu 25.04 / i7-1165G7 (Iris Xe): lavfi smoke per PORTING.md (idle->loading->playing snapshots at 4 Hz, aspect-fit generation bump g1 1280x720 -> g2 960x720 for a 4:3 source), reader probe 60 fps at 1080p60 with 0 torn reads, clean quit with no leaked processes or shm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(embedded-mpv): enable the frame-copy engine gates on Linux Flip the TypeScript side of the Linux port (PORTING.md item 5). A shared dependency-free predicate, isFrameCopyPlatformSupported() (linux any-arch, darwin arm64-only), now backs all four gates so they cannot drift: - main.ts: the persisted Settings toggle promotes to the env flag on Linux too (this runs before window creation and controls the sandbox relax). - EmbeddedMpvNativeService.isFrameCopyEngineActive/isFrameCopyAvailable. - EmbeddedMpvFrameCopyAdapter.isSupported. getSupport() ordering: the frame-copy branch moves above the Linux-only native-engine prerequisites - the X11/Xwayland display-server check and the system-mpv-on-PATH probe only bind the --wid native engine, while the frame-copy helper renders offscreen (headless EGL) and links libmpv itself. createSession() also skips resolving the native window handle for frame-copy sessions, which the adapter ignores anyway, so native-Wayland sessions no longer trip the window-handle assertion. Settings copy: the i18n frame-copy description now says macOS (Apple Silicon) and Linux in all 18 languages; stale macOS-only doc comments in the settings/support interfaces updated alongside. Tests: platform-gate matrix for the adapter (darwin arm64/x64, linux x64/arm64, win32) and service specs covering Linux activation under native Wayland, macOS arm64 staying active, macOS x64 staying native, and the skipped window handle for frame-copy sessions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(packaging): CI + package guards for the Linux frame-copy helper - build-and-make.yaml: install libegl-dev/libgl-dev/libgbm-dev on the Linux runner (the helper's EGL backend needs them now that the helper target builds on Linux), and verify the built helper exists and DOES link libmpv - the inverse of the addon's no-libmpv rule, which still holds and stays validated. - electron-after-pack.cjs: strip iptvnator_mpv_helper from packaged Linux apps. It links the build host's system libmpv, which end-user systems cannot be assumed to have; the support probe treats the missing helper as frame-copy-unavailable (dev-build-only engine until the bundled-runtime staging milestone). - frame_helper_gl.h: log the chosen EGL display tier to stderr (the adapter mirrors helper stderr), so bring-up problems on exotic setups are diagnosable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(embedded-mpv): document the Linux frame-copy port - architecture doc: frame-copy section covers Linux (EGL display tiers, build deps, package strip), Linux support matrix notes the frame-copy exception to the X11 + system-mpv requirements, Linux measured baseline. - RESULTS.md: Ubuntu 25.04 / i7-1165G7 (Iris Xe) measurement rows via the production helper + reader probe; viewport-size claim reproduced. - PORTING.md: Linux marked done with pointers to what changed; Windows remains the open port and its perf gate the open decision. - CLAUDE.md + tools/embedded-mpv/README.md: platform scope, Linux dev build requirements, system-headers fallback, helper strip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(embedded-mpv): commit the Linux frame-copy measurement probe linux-frame-probe.mjs reproduces the RESULTS.md Linux rows: spawns the production helper, attaches the frame-reader addon to the announced shm generation, and reports new-frame fps, copy wall time, produce->copy age, torn reads and pixel spread. Usage documented in RESULTS.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): address multi-agent review findings on the Linux port Confirmed findings (each verified by 3 adversarial reviewers): - CI would fail to link the helper: -lOpenGL needs the unversioned glvnd libOpenGL.so, shipped only by libopengl-dev, which neither the runner images nor the previous apt line provide. Added to the workflow and to every documented Linux build-dep list. - The new 'test -x' dist guard could never pass: webpack's dist asset copy drops file modes (helper arrives as 0644). The guard is now 'test -f'; electron-after-pack.cjs restores the execute bit on packaged helpers (also fixes packaged-macOS spawns); the support probe now requires X_OK, so a mode-stripped helper reads as frame-copy-unavailable and falls back to native instead of failing spawn with EACCES. - The Settings frame-copy toggle was unreachable in exactly the Linux states the port targets: the native-Wayland and missing-system-mpv unsupported payloads omitted frameCopyAvailable, and toggle visibility derives solely from it. Both returns now advertise availability. Also from review: - build-embedded-mpv.js keeps the old graceful-skip contract when the new system-dev fallback finds libmpv-dev but the GL/EGL/gbm dev stack is missing (previously such machines skipped; a hard electron-build failure was a regression). - createSession derives the window-handle skip from the dispatched addon instead of re-evaluating the engine gate, so the two cannot disagree. - The render thread logs the GL renderer string (surfaceless Mesa can silently pick llvmpipe on non-Mesa-primary systems; now diagnosable — verified 'Mesa Intel Iris Xe' on this machine). - Specs pin the new semantics: frameCopyAvailable advertised while native is unsupported (Wayland / no mpv), frame-copy supported without a system mpv, and the handle-skip test disposes its session through the owning adapter. - Docs: PORTING.md file map reflects the frame_helper_gl.h seam for the Windows porter; helper-strip removal correctly gated on milestone 4 (bundled libmpv), not milestone 3; RESULTS.md preamble notes the RAW->MONOTONIC clock change; stale '(macOS)' scope comments updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): address Greptile/Codex review comments - Sandbox gate requires a usable helper (Greptile P1, security): the main.ts env promotion now also probes for an executable iptvnator_mpv_helper before relaxing the window sandbox — a stale opt-in on packaged Linux (helper deliberately stripped) or after a cleaned native build no longer costs a sandboxless launch for an engine that cannot activate. Helper discovery (addon candidate paths + X_OK probe) moved into embedded-mpv-frame-copy-platform.util.ts, shared by main.ts and the service; the service keeps thin instance wrappers so tests can stub per scenario. New util spec pins the platform matrix, candidate resolution, and the execute-bit semantics. - Stale frame-copy artifacts on skipped builds (Codex P2): cleanOutput() now also removes iptvnator_mpv_helper and embedded_mpv_frame_reader.node, so a failed/skipped rebuild cannot leave a previous helper advertising frame-copy support against a runtime the build just declared unavailable. - Multiarch default lib dir (Greptile P1, partially refuted): -l resolution never depended on our -L (the compiler's built-in search paths include the Debian/Ubuntu multiarch dir — proven by the green CI run linking with a nonexistent -L dir), but the system-dev fallback now defaults to /usr/lib/<multiarch-triple> when present so the -L flag and the helper's baked rpath point somewhere real. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(embedded-mpv): harden Linux frame-copy port --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
854 lines
42 KiB
YAML
854 lines
42 KiB
YAML
name: Build and Make Electron App
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
tags:
|
|
- 'v*.*.*'
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build:
|
|
name: Build on ${{ matrix.os }} ${{ matrix.arch }}
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 120
|
|
strategy:
|
|
matrix:
|
|
include:
|
|
# macOS builds - separate runners to avoid native module conflicts
|
|
- os: macos
|
|
runner: macos-15-intel
|
|
arch: x64
|
|
embedded_mpv_platform: darwin
|
|
embedded_mpv_arch: x64
|
|
embedded_mpv_build_runtime: true
|
|
- os: macos
|
|
runner: macos-latest
|
|
arch: arm64
|
|
embedded_mpv_platform: darwin
|
|
embedded_mpv_arch: arm64
|
|
embedded_mpv_build_runtime: true
|
|
# Linux and Windows
|
|
- os: linux
|
|
runner: ubuntu-22.04
|
|
linux_profile: standard
|
|
embedded_mpv_platform: linux
|
|
embedded_mpv_arch: x64
|
|
embedded_mpv_build_runtime: false
|
|
- os: linux
|
|
runner: ubuntu-24.04
|
|
linux_profile: flatpak
|
|
embedded_mpv_platform: linux
|
|
embedded_mpv_arch: x64
|
|
embedded_mpv_build_runtime: false
|
|
- os: windows
|
|
runner: windows-2022
|
|
embedded_mpv_platform: win32
|
|
embedded_mpv_arch: x64
|
|
embedded_mpv_build_runtime: false
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install pnpm
|
|
uses: pnpm/action-setup@v4
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: '22'
|
|
cache: 'pnpm'
|
|
|
|
- name: Install Linux system dependencies
|
|
if: matrix.os == 'linux'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install --no-install-recommends -y rpm libarchive-tools flatpak flatpak-builder appstream libx11-dev libxext-dev libmpv-dev mpv pkg-config libegl-dev libgl-dev libopengl-dev libgbm-dev
|
|
|
|
# Configure Flatpak
|
|
# 1. Add the Flathub repository (source of runtimes)
|
|
flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo
|
|
|
|
# 2. Install the standard Freedesktop Platform and SDK (required by electron-builder)
|
|
# We install version 24.08 as a safe default, electron-builder might pick what it needs
|
|
flatpak install --user -y flathub org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08
|
|
|
|
- name: Select Linux packaging targets for CI profile
|
|
if: matrix.os == 'linux'
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const path = 'electron-builder.json';
|
|
const config = JSON.parse(fs.readFileSync(path, 'utf8'));
|
|
const targets = Array.isArray(config.linux?.target) ? config.linux.target : [];
|
|
const profile = '${{ matrix.linux_profile }}';
|
|
|
|
if (profile === 'standard') {
|
|
config.linux.target = targets.filter((entry) => String(entry.target).toLowerCase() !== 'flatpak');
|
|
} else if (profile === 'flatpak') {
|
|
config.linux.target = targets.filter((entry) => String(entry.target).toLowerCase() === 'flatpak');
|
|
}
|
|
|
|
fs.writeFileSync(path, JSON.stringify(config, null, 4) + '\n');
|
|
"
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Inject TMDB API key
|
|
# No-op when the secret is unavailable (e.g. fork PRs) — the
|
|
# app then requires a user-provided key for TMDB enrichment.
|
|
env:
|
|
TMDB_API_KEY: ${{ secrets.TMDB_API_KEY }}
|
|
run: node tools/tmdb/inject-tmdb-key.mjs
|
|
|
|
- name: Build frontend
|
|
run: pnpm nx build web --skip-nx-cache
|
|
|
|
- name: Resolve embedded MPV runtime cache key
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
if: matrix.embedded_mpv_platform && !startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
|
id: embedded-mpv-runtime-cache-key
|
|
shell: bash
|
|
env:
|
|
IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: ${{ vars.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || secrets.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || '' }}
|
|
IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: a26e28fa55c15ac5b6209d8a9f9c3c8cb39649631a53d3c4774beeae559a078c
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
node <<'NODE'
|
|
const childProcess = require('child_process');
|
|
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
|
|
const hash = (value) => crypto.createHash('sha256').update(value).digest('hex');
|
|
const targetPlatform = '${{ matrix.embedded_mpv_platform }}';
|
|
const targetArch = '${{ matrix.embedded_mpv_arch }}';
|
|
const sourceHashFiles = [
|
|
'tools/embedded-mpv/stage-runtime.mjs',
|
|
];
|
|
const cacheKeyParts = [
|
|
'embedded-mpv-runtime-v2',
|
|
targetPlatform,
|
|
targetArch,
|
|
process.env.RUNNER_OS,
|
|
];
|
|
let deploymentTarget = '';
|
|
let xcodeVersion = 'none';
|
|
if (targetPlatform === 'darwin') {
|
|
deploymentTarget = process.env.MACOSX_DEPLOYMENT_TARGET || '11.0';
|
|
const safeDeploymentTarget = deploymentTarget.replace(/[^A-Za-z0-9_.-]/g, '-');
|
|
sourceHashFiles.push(
|
|
'tools/embedded-mpv/build-macos-runtime.mjs',
|
|
'tools/embedded-mpv/stage-macos-runtime.mjs'
|
|
);
|
|
try {
|
|
xcodeVersion = childProcess
|
|
.execSync('xcodebuild -version', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
|
|
.replace(/\s+$/g, '')
|
|
.replace(/\n/g, ' ');
|
|
} catch {
|
|
xcodeVersion = 'none';
|
|
}
|
|
cacheKeyParts.push(
|
|
`macos${safeDeploymentTarget}`,
|
|
`xcode${hash(xcodeVersion)}`
|
|
);
|
|
} else if (targetPlatform === 'win32') {
|
|
sourceHashFiles.push('tools/embedded-mpv/stage-windows-runtime-archive.mjs');
|
|
const windowsRuntimeSha256 =
|
|
process.env.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 ||
|
|
process.env.IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 ||
|
|
'missing';
|
|
cacheKeyParts.push(
|
|
`runtime${hash(windowsRuntimeSha256)}`
|
|
);
|
|
}
|
|
const sourceHash = hash(
|
|
sourceHashFiles.map((filePath) => fs.readFileSync(filePath)).join('\n')
|
|
);
|
|
cacheKeyParts.push(sourceHash);
|
|
const cacheKey = cacheKeyParts.join('-');
|
|
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `deployment-target=${deploymentTarget}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `xcode-version=${xcodeVersion}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `key=${cacheKey}\n`);
|
|
console.log(`Embedded MPV runtime cache key: ${cacheKey}`);
|
|
console.log(`Xcode version: ${xcodeVersion}`);
|
|
NODE
|
|
|
|
- name: Restore embedded MPV runtime cache
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
if: matrix.embedded_mpv_platform && !startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
|
id: embedded-mpv-runtime-cache
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: |
|
|
vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/include
|
|
vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/lib
|
|
vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/runtime-manifest.json
|
|
key: ${{ steps.embedded-mpv-runtime-cache-key.outputs.key }}
|
|
|
|
- name: Clear stale embedded MPV runtime files
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
if: matrix.embedded_mpv_platform && !startsWith(github.ref, 'refs/tags/v') && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master') && steps.embedded-mpv-runtime-cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
rm -rf \
|
|
"vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/include" \
|
|
"vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/lib" \
|
|
"vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/runtime-manifest.json"
|
|
|
|
- name: Build embedded MPV runtime (macOS release)
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
if: matrix.embedded_mpv_build_runtime && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' || github.ref == 'refs/heads/master') && steps.embedded-mpv-runtime-cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
brew install meson ninja pkg-config nasm autoconf automake libtool xz
|
|
|
|
RUNTIME_PREFIX="${RUNNER_TEMP}/embedded-mpv-runtime/${{ matrix.embedded_mpv_arch }}/prefix"
|
|
pnpm embedded-mpv:build-runtime -- "${{ matrix.embedded_mpv_arch }}" "${RUNTIME_PREFIX}"
|
|
pnpm embedded-mpv:stage-runtime -- "${{ matrix.embedded_mpv_platform }}" "${{ matrix.embedded_mpv_arch }}" "${RUNTIME_PREFIX}"
|
|
|
|
- name: Stage Windows embedded MPV runtime archive
|
|
if: matrix.os == 'windows' && steps.embedded-mpv-runtime-cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
env:
|
|
IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL: ${{ vars.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL || secrets.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL || '' }}
|
|
IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: ${{ vars.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || secrets.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || '' }}
|
|
IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_URL: https://github.com/zhongfly/mpv-winbuild/releases/download/2026-06-14-7d245fd100/mpv-dev-lgpl-x86_64-20260614-git-7d245fd100.7z
|
|
IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: a26e28fa55c15ac5b6209d8a9f9c3c8cb39649631a53d3c4774beeae559a078c
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
WINDOWS_RUNTIME_URL="${IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL}"
|
|
WINDOWS_RUNTIME_SHA256="${IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256}"
|
|
if [ -z "${WINDOWS_RUNTIME_URL}" ] && [ -z "${WINDOWS_RUNTIME_SHA256}" ]; then
|
|
case "${GITHUB_REF}" in
|
|
refs/tags/v*)
|
|
;;
|
|
*)
|
|
WINDOWS_RUNTIME_URL="${IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_URL}"
|
|
WINDOWS_RUNTIME_SHA256="${IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256}"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [ -z "${WINDOWS_RUNTIME_URL}" ] || [ -z "${WINDOWS_RUNTIME_SHA256}" ]; then
|
|
echo "::error::Windows Embedded MPV CI requires IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL and IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 repository variables or secrets."
|
|
exit 1
|
|
fi
|
|
|
|
pnpm embedded-mpv:stage-runtime:windows-archive -- "${WINDOWS_RUNTIME_URL}" "${WINDOWS_RUNTIME_SHA256}"
|
|
|
|
- name: Stage Linux embedded MPV build inputs
|
|
if: matrix.os == 'linux'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
RUNTIME_PREFIX="${RUNNER_TEMP}/embedded-mpv-runtime/linux-x64/prefix"
|
|
rm -rf "${RUNTIME_PREFIX}"
|
|
mkdir -p "${RUNTIME_PREFIX}/include"
|
|
|
|
cp -a /usr/include/mpv "${RUNTIME_PREFIX}/include/"
|
|
|
|
LIBMPV_DEV_VERSION="$(dpkg-query -W -f='${Version}' libmpv-dev)"
|
|
MPV_VERSION="$(dpkg-query -W -f='${Version}' mpv)"
|
|
export RUNTIME_PREFIX LIBMPV_DEV_VERSION MPV_VERSION
|
|
node <<'NODE'
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const manifest = {
|
|
linuxBackend: 'process-isolated mpv --wid',
|
|
buildInputs: {
|
|
libmpvDevPackage: process.env.LIBMPV_DEV_VERSION,
|
|
mpvPackage: process.env.MPV_VERSION,
|
|
},
|
|
sourceDistribution:
|
|
'Linux CI build inputs come from Ubuntu runner packages. Runtime playback uses the system mpv executable; IPTVnator does not bundle or load libmpv in the Electron process on Linux.',
|
|
};
|
|
|
|
fs.writeFileSync(
|
|
path.join(process.env.RUNTIME_PREFIX, 'runtime-manifest.json'),
|
|
`${JSON.stringify(manifest, null, 2)}\n`
|
|
);
|
|
NODE
|
|
|
|
pnpm embedded-mpv:stage-runtime -- linux x64 "${RUNTIME_PREFIX}"
|
|
|
|
- name: Build backend
|
|
env:
|
|
IPTVNATOR_EMBEDDED_MPV_PLATFORM: ${{ matrix.embedded_mpv_platform || '' }}
|
|
IPTVNATOR_EMBEDDED_MPV_ARCH: ${{ matrix.embedded_mpv_arch || matrix.arch || '' }}
|
|
IPTVNATOR_REQUIRE_EMBEDDED_MPV: ${{ (matrix.os == 'linux' || matrix.os == 'windows' || (matrix.os == 'macos' && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' || github.ref == 'refs/heads/master'))) && '1' || '0' }}
|
|
run: pnpm run build:backend
|
|
|
|
- name: Verify embedded MPV build output
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
if: matrix.os == 'linux' || matrix.os == 'windows' || (matrix.os == 'macos' && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' || github.ref == 'refs/heads/master'))
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
echo "::group::Native build output"
|
|
find apps/electron-backend/native/build/Release -maxdepth 3 -type f | sort
|
|
echo "::endgroup::"
|
|
|
|
echo "::group::Dist native output"
|
|
find dist/apps/electron-backend/native -maxdepth 3 -type f | sort
|
|
echo "::endgroup::"
|
|
|
|
test -f apps/electron-backend/native/build/Release/embedded_mpv.node
|
|
test -f dist/apps/electron-backend/native/embedded_mpv.node
|
|
test -f dist/apps/electron-backend/native/embedded-mpv-runtime.json
|
|
case "${{ matrix.embedded_mpv_platform }}" in
|
|
darwin)
|
|
test -f dist/apps/electron-backend/native/lib/libmpv.2.dylib || test -f dist/apps/electron-backend/native/lib/libmpv.dylib
|
|
;;
|
|
win32)
|
|
test -f dist/apps/electron-backend/native/lib/mpv-2.dll || test -f dist/apps/electron-backend/native/lib/libmpv-2.dll || test -f dist/apps/electron-backend/native/lib/mpv.dll || test -f dist/apps/electron-backend/native/lib/libmpv.dll
|
|
;;
|
|
linux)
|
|
node -e "const manifest = require('./dist/apps/electron-backend/native/embedded-mpv-runtime.json'); if (manifest.origin !== 'external-mpv-process') { throw new Error('Linux embedded MPV manifest must use external-mpv-process origin.'); }"
|
|
if find dist/apps/electron-backend/native/lib -name 'libmpv.so*' -print -quit 2>/dev/null | grep -q .; then
|
|
echo "::error::Linux embedded MPV packages must not bundle libmpv"
|
|
find dist/apps/electron-backend/native/lib -name 'libmpv.so*' -print
|
|
exit 1
|
|
fi
|
|
if ldd dist/apps/electron-backend/native/embedded_mpv.node | grep -q 'libmpv'; then
|
|
echo "::error::Linux embedded MPV addon must not link directly to libmpv"
|
|
ldd dist/apps/electron-backend/native/embedded_mpv.node
|
|
exit 1
|
|
fi
|
|
# The frame-copy helper is the inverse: a separate process
|
|
# that MUST link libmpv (dev-mode engine; stripped from
|
|
# packages until the bundled-runtime staging lands).
|
|
# test -f, not -x: the webpack dist asset copy drops file
|
|
# modes; consumers restore the bit (after-pack) or require
|
|
# it via the X_OK support probe.
|
|
test -f dist/apps/electron-backend/native/iptvnator_mpv_helper
|
|
if ! ldd dist/apps/electron-backend/native/iptvnator_mpv_helper | grep -q 'libmpv'; then
|
|
echo "::error::Linux frame-copy helper must link libmpv"
|
|
ldd dist/apps/electron-backend/native/iptvnator_mpv_helper
|
|
exit 1
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
- name: Validate AppStream metadata
|
|
if: matrix.os == 'linux'
|
|
run: appstreamcli validate --pedantic --no-net apps/electron-backend/linux/com.fourgray.iptvnator.metainfo.xml
|
|
|
|
- name: Build website
|
|
if: matrix.os == 'linux'
|
|
run: pnpm nx build website --skip-nx-cache
|
|
|
|
- name: Verify AppStream website assets
|
|
if: matrix.os == 'linux'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
for screenshot in player playlists epg settings; do
|
|
if ! find dist/apps/website -path "*/appstream/${screenshot}.png" -print -quit | grep -q .; then
|
|
echo "::error::Missing AppStream website asset for ${screenshot}.png"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: Override macOS arch in electron-builder.json
|
|
if: matrix.os == 'macos'
|
|
run: |
|
|
# Replace the mac arch array with just the target architecture
|
|
node -e "
|
|
const fs = require('fs');
|
|
const pkg = JSON.parse(fs.readFileSync('electron-builder.json', 'utf8'));
|
|
const targets = Array.isArray(pkg.mac?.target) ? pkg.mac.target : [pkg.mac.target];
|
|
for (const target of targets) {
|
|
if (target && typeof target === 'object') {
|
|
target.arch = ['${{ matrix.arch }}'];
|
|
}
|
|
}
|
|
fs.writeFileSync('electron-builder.json', JSON.stringify(pkg, null, 4));
|
|
"
|
|
|
|
- name: Override Windows arch in electron-builder.json
|
|
if: matrix.os == 'windows'
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const pkg = JSON.parse(fs.readFileSync('electron-builder.json', 'utf8'));
|
|
const targets = Array.isArray(pkg.win?.target) ? pkg.win.target : [];
|
|
for (const target of targets) {
|
|
if (target && typeof target === 'object') {
|
|
target.arch = ['${{ matrix.embedded_mpv_arch }}'];
|
|
}
|
|
}
|
|
fs.writeFileSync('electron-builder.json', JSON.stringify(pkg, null, 4) + '\n');
|
|
"
|
|
|
|
- name: Validate macOS signing configuration
|
|
if: matrix.os == 'macos' && github.event_name != 'pull_request'
|
|
shell: bash
|
|
env:
|
|
CSC_NAME: ${{ vars.CSC_NAME }}
|
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }}
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
if [ -z "${CSC_NAME}" ]; then
|
|
echo "::error::Missing CSC_NAME repository variable for deterministic macOS code signing."
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "${CSC_LINK}" ] || [ -z "${CSC_KEY_PASSWORD}" ]; then
|
|
echo "::error::Missing CSC_LINK or CSC_KEY_PASSWORD secret for macOS code signing."
|
|
exit 1
|
|
fi
|
|
|
|
has_api_key_credentials=false
|
|
if [ -n "${APPLE_API_KEY_CONTENT}" ] || [ -n "${APPLE_API_KEY_ID}" ] || [ -n "${APPLE_API_ISSUER}" ]; then
|
|
if [ -z "${APPLE_API_KEY_CONTENT}" ] || [ -z "${APPLE_API_KEY_ID}" ] || [ -z "${APPLE_API_ISSUER}" ]; then
|
|
echo "::error::APPLE_API_KEY, APPLE_API_KEY_ID, and APPLE_API_ISSUER must all be set for App Store Connect API key notarization."
|
|
exit 1
|
|
fi
|
|
has_api_key_credentials=true
|
|
fi
|
|
|
|
has_apple_id_credentials=false
|
|
if [ -n "${APPLE_ID}" ] || [ -n "${APPLE_APP_SPECIFIC_PASSWORD}" ] || [ -n "${APPLE_TEAM_ID}" ]; then
|
|
if [ -z "${APPLE_ID}" ] || [ -z "${APPLE_APP_SPECIFIC_PASSWORD}" ] || [ -z "${APPLE_TEAM_ID}" ]; then
|
|
echo "::error::APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, and APPLE_TEAM_ID must all be set for Apple ID notarization."
|
|
exit 1
|
|
fi
|
|
has_apple_id_credentials=true
|
|
fi
|
|
|
|
if [ "${has_api_key_credentials}" = false ] && [ "${has_apple_id_credentials}" = false ]; then
|
|
echo "::error::Missing notarization credentials. Configure either APPLE_API_KEY + APPLE_API_KEY_ID + APPLE_API_ISSUER, or APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD + APPLE_TEAM_ID."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Prepare macOS notarization credentials
|
|
if: matrix.os == 'macos' && github.event_name != 'pull_request'
|
|
shell: bash
|
|
env:
|
|
APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY }}
|
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
run: |
|
|
if [ -n "${APPLE_API_KEY_CONTENT}" ]; then
|
|
APPLE_API_KEY_PATH="${RUNNER_TEMP}/AuthKey_${APPLE_API_KEY_ID}.p8"
|
|
printf '%s' "${APPLE_API_KEY_CONTENT}" > "${APPLE_API_KEY_PATH}"
|
|
chmod 600 "${APPLE_API_KEY_PATH}"
|
|
echo "APPLE_API_KEY=${APPLE_API_KEY_PATH}" >> "${GITHUB_ENV}"
|
|
echo "APPLE_API_KEY_ID=${APPLE_API_KEY_ID}" >> "${GITHUB_ENV}"
|
|
echo "APPLE_API_ISSUER=${APPLE_API_ISSUER}" >> "${GITHUB_ENV}"
|
|
echo "APPLE_ID=" >> "${GITHUB_ENV}"
|
|
echo "APPLE_APP_SPECIFIC_PASSWORD=" >> "${GITHUB_ENV}"
|
|
echo "APPLE_TEAM_ID=" >> "${GITHUB_ENV}"
|
|
exit 0
|
|
fi
|
|
|
|
if [ -n "${APPLE_ID}" ]; then
|
|
echo "APPLE_API_KEY=" >> "${GITHUB_ENV}"
|
|
echo "APPLE_API_KEY_ID=" >> "${GITHUB_ENV}"
|
|
echo "APPLE_API_ISSUER=" >> "${GITHUB_ENV}"
|
|
echo "APPLE_ID=${APPLE_ID}" >> "${GITHUB_ENV}"
|
|
echo "APPLE_APP_SPECIFIC_PASSWORD=${APPLE_APP_SPECIFIC_PASSWORD}" >> "${GITHUB_ENV}"
|
|
echo "APPLE_TEAM_ID=${APPLE_TEAM_ID}" >> "${GITHUB_ENV}"
|
|
fi
|
|
|
|
- name: Make Electron app (macOS)
|
|
if: matrix.os == 'macos' && github.event_name != 'pull_request'
|
|
env:
|
|
CSC_NAME: ${{ vars.CSC_NAME }}
|
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
|
DEBUG: electron-builder,electron-notarize*
|
|
IPTVNATOR_EMBEDDED_MPV_PLATFORM: ${{ matrix.embedded_mpv_platform }}
|
|
IPTVNATOR_EMBEDDED_MPV_ARCH: ${{ matrix.embedded_mpv_arch }}
|
|
# TEMPORARY MASTER ARTIFACT TEST: remove `|| github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
IPTVNATOR_REQUIRE_EMBEDDED_MPV: ${{ (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') && '1' || '0' }}
|
|
run: pnpm run make:app -- --publishPolicy=never
|
|
|
|
- name: Verify signed macOS app
|
|
if: matrix.os == 'macos' && github.event_name != 'pull_request'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
case "${{ matrix.arch }}" in
|
|
x64)
|
|
APP_PATH="dist/executables/mac/IPTVnator.app"
|
|
;;
|
|
arm64)
|
|
APP_PATH="dist/executables/mac-arm64/IPTVnator.app"
|
|
;;
|
|
*)
|
|
echo "::error::Unsupported macOS arch: ${{ matrix.arch }}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
print_debug_attrs() {
|
|
echo "::group::Extended attributes"
|
|
xattr -lr "${APP_PATH}" | sed -n '1,120p' || true
|
|
echo "::endgroup::"
|
|
}
|
|
|
|
trap print_debug_attrs ERR
|
|
|
|
if [ ! -d "${APP_PATH}" ]; then
|
|
echo "::error::Expected app bundle not found at ${APP_PATH}"
|
|
exit 1
|
|
fi
|
|
|
|
SIGNATURE_INFO="$(codesign -dv --verbose=4 "${APP_PATH}" 2>&1)"
|
|
printf '%s\n' "${SIGNATURE_INFO}"
|
|
|
|
if printf '%s\n' "${SIGNATURE_INFO}" | grep -q 'Signature=adhoc'; then
|
|
echo "::error::macOS app is still ad-hoc signed."
|
|
exit 1
|
|
fi
|
|
|
|
if printf '%s\n' "${SIGNATURE_INFO}" | grep -q 'TeamIdentifier=not set'; then
|
|
echo "::error::macOS app is missing a TeamIdentifier."
|
|
exit 1
|
|
fi
|
|
|
|
codesign --verify --deep --strict --verbose=4 "${APP_PATH}"
|
|
spctl -a -vvv --type execute "${APP_PATH}"
|
|
xcrun stapler validate "${APP_PATH}"
|
|
|
|
- name: Make Electron app
|
|
if: matrix.os != 'macos' || github.event_name == 'pull_request'
|
|
env:
|
|
IPTVNATOR_EMBEDDED_MPV_PLATFORM: ${{ matrix.embedded_mpv_platform || '' }}
|
|
IPTVNATOR_EMBEDDED_MPV_ARCH: ${{ matrix.embedded_mpv_arch || matrix.arch || '' }}
|
|
# TEMPORARY PR TEST: change this back to '0' after manually
|
|
# testing the macOS PR artifact with Embedded MPV included.
|
|
IPTVNATOR_REQUIRE_EMBEDDED_MPV: ${{ (matrix.os == 'linux' || matrix.os == 'windows' || (matrix.os == 'macos' && github.event_name == 'pull_request')) && '1' || '0' }}
|
|
run: pnpm run make:app -- --publishPolicy=never
|
|
|
|
- name: Verify packaged worker layout
|
|
shell: bash
|
|
env:
|
|
PACKAGE_OS: ${{ matrix.os }}
|
|
PACKAGE_ARCH: ${{ matrix.arch || matrix.embedded_mpv_arch || '' }}
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
IPTVNATOR_REQUIRE_EMBEDDED_MPV: ${{ (matrix.os == 'linux' || matrix.os == 'windows' || (matrix.os == 'macos' && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'pull_request' || github.ref == 'refs/heads/master'))) && '1' || '0' }}
|
|
run: pnpm run verify:package-layout -- "$PACKAGE_OS" "$PACKAGE_ARCH"
|
|
|
|
- name: Save embedded MPV runtime cache
|
|
# TEMPORARY ARTIFACT TEST: remove `|| github.ref == 'refs/heads/master'`
|
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
|
if: matrix.embedded_mpv_platform && matrix.os != 'linux' && !startsWith(github.ref, 'refs/tags/v') && github.repository == '4gray/iptvnator' && github.event_name != 'pull_request' && github.ref == 'refs/heads/master' && steps.embedded-mpv-runtime-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v4
|
|
with:
|
|
path: |
|
|
vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/include
|
|
vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/lib
|
|
vendor/embedded-mpv/${{ matrix.embedded_mpv_platform }}-${{ matrix.embedded_mpv_arch }}/runtime-manifest.json
|
|
key: ${{ steps.embedded-mpv-runtime-cache-key.outputs.key }}
|
|
|
|
- name: Smoke test packaged Flatpak launcher
|
|
if: matrix.os == 'linux' && matrix.linux_profile == 'flatpak'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
FLATPAK_BUNDLE="$(find dist/executables -maxdepth 1 -name '*.flatpak' | head -n 1)"
|
|
if [ -z "${FLATPAK_BUNDLE}" ]; then
|
|
echo "::error::Flatpak bundle not found in dist/executables"
|
|
exit 1
|
|
fi
|
|
|
|
flatpak install --user --noninteractive -y "${FLATPAK_BUNDLE}"
|
|
flatpak run --command=sh com.fourgray.iptvnator -c '
|
|
set -euo pipefail
|
|
|
|
test -f /app/share/metainfo/com.fourgray.iptvnator.metainfo.xml
|
|
test -L /app/bin/iptvnator
|
|
|
|
LAUNCHER_PATH="$(readlink -f /app/bin/iptvnator)"
|
|
test -f "${LAUNCHER_PATH}"
|
|
test -f "${LAUNCHER_PATH}.bin"
|
|
grep -q '\''readlink -f "$SCRIPT_PATH"'\'' "${LAUNCHER_PATH}"
|
|
grep -q '\''exec "$SCRIPT_DIR/iptvnator.bin"'\'' "${LAUNCHER_PATH}"
|
|
'
|
|
|
|
- name: Upload artifacts (macOS)
|
|
if: matrix.os == 'macos'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: macos-${{ matrix.arch }}-artifacts
|
|
path: |
|
|
dist/executables/**/*.dmg
|
|
dist/executables/**/*.zip
|
|
dist/executables/**/latest-mac.yml
|
|
dist/executables/**/*.blockmap
|
|
retention-days: 7
|
|
|
|
- name: Upload artifacts (Linux)
|
|
if: matrix.os == 'linux' && matrix.linux_profile == 'standard'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: linux-artifacts
|
|
path: |
|
|
dist/executables/**/*.deb
|
|
dist/executables/**/*.rpm
|
|
dist/executables/**/*.snap
|
|
dist/executables/**/*.AppImage
|
|
dist/executables/**/*.tar.gz
|
|
dist/executables/**/*.pacman
|
|
dist/executables/**/latest-linux*.yml
|
|
dist/executables/**/*.blockmap
|
|
retention-days: 7
|
|
|
|
- name: Upload artifacts (Flatpak)
|
|
if: matrix.os == 'linux' && matrix.linux_profile == 'flatpak'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: linux-flatpak-artifacts
|
|
path: |
|
|
dist/executables/**/*.flatpak
|
|
retention-days: 7
|
|
|
|
- name: Upload artifacts (Windows)
|
|
if: matrix.os == 'windows'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: windows-artifacts
|
|
path: |
|
|
dist/executables/**/*.exe
|
|
dist/executables/**/*.msi
|
|
dist/executables/**/*.zip
|
|
dist/executables/**/latest.yml
|
|
dist/executables/**/*.blockmap
|
|
retention-days: 7
|
|
|
|
create-release:
|
|
name: Create Draft Release
|
|
needs: build
|
|
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Download all artifacts
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: Display structure of downloaded files
|
|
run: ls -R artifacts
|
|
|
|
- name: Merge macOS updater metadata
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
node <<'NODE'
|
|
const fs = require('fs');
|
|
|
|
const candidates = [
|
|
'artifacts/macos-x64-artifacts/latest-mac.yml',
|
|
'artifacts/macos-arm64-artifacts/latest-mac.yml',
|
|
].filter((filePath) => fs.existsSync(filePath));
|
|
|
|
if (candidates.length === 0) {
|
|
console.log('No macOS update metadata found; skipping merge.');
|
|
process.exit(0);
|
|
}
|
|
|
|
function extractFilesBlock(text, filePath) {
|
|
const lines = text.split(/\r?\n/);
|
|
const start = lines.findIndex((line) => line === 'files:');
|
|
|
|
if (start === -1) {
|
|
throw new Error(`Missing files block in ${filePath}`);
|
|
}
|
|
|
|
const block = [];
|
|
|
|
for (let index = start + 1; index < lines.length; index += 1) {
|
|
const line = lines[index];
|
|
|
|
if (/^\S/.test(line) && line.trim() !== '') {
|
|
break;
|
|
}
|
|
|
|
if (line.trim() !== '') {
|
|
block.push(line);
|
|
}
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
function splitFileEntries(block) {
|
|
const entries = [];
|
|
let current = [];
|
|
|
|
for (const line of block) {
|
|
if (/^\s*-\s+url:/.test(line) && current.length > 0) {
|
|
entries.push(current);
|
|
current = [];
|
|
}
|
|
|
|
current.push(line);
|
|
}
|
|
|
|
if (current.length > 0) {
|
|
entries.push(current);
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
function replaceFilesBlock(text, mergedBlock) {
|
|
const lines = text.split(/\r?\n/);
|
|
const start = lines.findIndex((line) => line === 'files:');
|
|
let end = lines.length;
|
|
|
|
for (let index = start + 1; index < lines.length; index += 1) {
|
|
const line = lines[index];
|
|
|
|
if (/^\S/.test(line) && line.trim() !== '') {
|
|
end = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return [
|
|
...lines.slice(0, start + 1),
|
|
...mergedBlock,
|
|
...lines.slice(end),
|
|
].join('\n').replace(/\n*$/, '\n');
|
|
}
|
|
|
|
const mergedEntries = [];
|
|
const seenUrls = new Set();
|
|
|
|
for (const filePath of candidates) {
|
|
const text = fs.readFileSync(filePath, 'utf8');
|
|
const entries = splitFileEntries(extractFilesBlock(text, filePath));
|
|
|
|
for (const entry of entries) {
|
|
const urlLine = entry.find((line) => /^\s*-\s+url:/.test(line));
|
|
const url = urlLine?.replace(/^\s*-\s+url:\s*/, '').trim();
|
|
|
|
if (!url || seenUrls.has(url)) {
|
|
continue;
|
|
}
|
|
|
|
seenUrls.add(url);
|
|
mergedEntries.push(...entry);
|
|
}
|
|
}
|
|
|
|
const merged = replaceFilesBlock(
|
|
fs.readFileSync(candidates[0], 'utf8'),
|
|
mergedEntries
|
|
);
|
|
|
|
fs.writeFileSync('artifacts/latest-mac.yml', merged);
|
|
console.log(`Merged ${candidates.length} macOS update metadata files.`);
|
|
NODE
|
|
|
|
- name: Get version from package.json
|
|
id: package-version
|
|
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
|
|
|
- name: Create Draft Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
draft: true
|
|
prerelease: ${{ github.event_name == 'pull_request' }}
|
|
name: Release v${{ steps.package-version.outputs.version }}
|
|
tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('test-{0}', github.sha) }}
|
|
generate_release_notes: true
|
|
files: |
|
|
artifacts/macos-x64-artifacts/*-x64.dmg
|
|
artifacts/macos-x64-artifacts/*-x64.zip
|
|
artifacts/macos-x64-artifacts/*.blockmap
|
|
artifacts/macos-arm64-artifacts/*-arm64.dmg
|
|
artifacts/macos-arm64-artifacts/*-arm64.zip
|
|
artifacts/macos-arm64-artifacts/*.blockmap
|
|
artifacts/latest-mac.yml
|
|
artifacts/linux-artifacts/*.AppImage
|
|
artifacts/linux-artifacts/*.deb
|
|
artifacts/linux-artifacts/*.rpm
|
|
artifacts/linux-artifacts/*.snap
|
|
artifacts/linux-artifacts/*.tar.gz
|
|
artifacts/linux-artifacts/*.pacman
|
|
artifacts/linux-artifacts/latest-linux*.yml
|
|
artifacts/linux-artifacts/*.blockmap
|
|
artifacts/linux-flatpak-artifacts/*.flatpak
|
|
artifacts/windows-artifacts/*-setup.exe
|
|
artifacts/windows-artifacts/*.msi
|
|
artifacts/windows-artifacts/*.zip
|
|
artifacts/windows-artifacts/latest.yml
|
|
artifacts/windows-artifacts/*.blockmap
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
publish-snap:
|
|
name: Publish to Snapcraft Store
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.snapcraft_token }}
|
|
|
|
steps:
|
|
- name: Download snap artifact
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: linux-artifacts
|
|
path: artifacts
|
|
|
|
- name: Setup Snapcraft
|
|
uses: samuelmeuli/action-snapcraft@v3
|
|
|
|
- name: Publish all snaps to edge channel
|
|
run: |
|
|
# Find and publish all snap files
|
|
for SNAP_FILE in artifacts/*.snap; do
|
|
if [ -f "$SNAP_FILE" ]; then
|
|
echo "Publishing: $SNAP_FILE"
|
|
snapcraft upload --release=edge "$SNAP_FILE"
|
|
fi
|
|
done
|