mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
ci(embedded-mpv): keep Windows runtime pin available (#1495)
This commit is contained in:
1 parent
29ca94aa43
commit
f04f67728e
14 files changed
+972
-75
No files matched your search
@@ -448,6 +448,12 @@ jobs:
|
|||||||
- name: Build frontend
|
- name: Build frontend
|
||||||
run: pnpm nx build web --skip-nx-cache
|
run: pnpm nx build web --skip-nx-cache
|
||||||
|
|
||||||
|
- name: Resolve pinned Windows Embedded MPV runtime
|
||||||
|
if: matrix.os == 'windows'
|
||||||
|
id: windows-embedded-mpv-runtime-pin
|
||||||
|
shell: bash
|
||||||
|
run: node tools/embedded-mpv/windows-runtime-pin.mjs --github-output
|
||||||
|
|
||||||
- name: Resolve embedded MPV runtime cache key
|
- name: Resolve embedded MPV runtime cache key
|
||||||
# TEMPORARY ARTIFACT TEST: remove `|| github.event_name == 'pull_request' || github.ref == 'refs/heads/master'`
|
# 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.
|
# after the macOS Embedded MPV artifacts are built and manually tested.
|
||||||
@@ -455,8 +461,7 @@ jobs:
|
|||||||
id: embedded-mpv-runtime-cache-key
|
id: embedded-mpv-runtime-cache-key
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: ${{ vars.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || secrets.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || '' }}
|
IPTVNATOR_PINNED_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: ${{ steps.windows-embedded-mpv-runtime-pin.outputs.sha256 || '' }}
|
||||||
IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: 6014aa0e6d8e98cdba90f5288295a7105d7d14ab0ca906f51465eeb478d5fea0
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -500,11 +505,13 @@ jobs:
|
|||||||
`xcode${hash(xcodeVersion)}`
|
`xcode${hash(xcodeVersion)}`
|
||||||
);
|
);
|
||||||
} else if (targetPlatform === 'win32') {
|
} else if (targetPlatform === 'win32') {
|
||||||
sourceHashFiles.push('tools/embedded-mpv/stage-windows-runtime-archive.mjs');
|
sourceHashFiles.push(
|
||||||
|
'tools/embedded-mpv/stage-windows-runtime-archive.mjs',
|
||||||
|
'tools/embedded-mpv/windows-runtime-pin.json',
|
||||||
|
'tools/embedded-mpv/windows-runtime-pin.mjs'
|
||||||
|
);
|
||||||
const windowsRuntimeSha256 =
|
const windowsRuntimeSha256 =
|
||||||
process.env.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 ||
|
process.env.IPTVNATOR_PINNED_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || 'missing';
|
||||||
process.env.IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 ||
|
|
||||||
'missing';
|
|
||||||
cacheKeyParts.push(
|
cacheKeyParts.push(
|
||||||
`runtime${hash(windowsRuntimeSha256)}`
|
`runtime${hash(windowsRuntimeSha256)}`
|
||||||
);
|
);
|
||||||
@@ -566,28 +573,13 @@ jobs:
|
|||||||
if: matrix.os == 'windows' && steps.embedded-mpv-runtime-cache.outputs.cache-hit != 'true'
|
if: matrix.os == 'windows' && steps.embedded-mpv-runtime-cache.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL: ${{ vars.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL || secrets.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL || '' }}
|
WINDOWS_RUNTIME_URL: ${{ steps.windows-embedded-mpv-runtime-pin.outputs.url }}
|
||||||
IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: ${{ vars.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || secrets.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256 || '' }}
|
WINDOWS_RUNTIME_SHA256: ${{ steps.windows-embedded-mpv-runtime-pin.outputs.sha256 }}
|
||||||
IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_URL: https://github.com/zhongfly/mpv-winbuild/releases/download/2026-07-17-94335ab87a/mpv-dev-lgpl-x86_64-20260717-git-94335ab87a.7z
|
|
||||||
IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256: 6014aa0e6d8e98cdba90f5288295a7105d7d14ab0ca906f51465eeb478d5fea0
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
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
|
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."
|
echo "::error::The checked-in Windows Embedded MPV runtime pin did not resolve a URL and SHA-256 digest."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
name: Refresh Windows Embedded MPV Runtime Pin
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '17 6 * * 1'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
force:
|
||||||
|
description: Refresh even when the current pin is still young
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: refresh-windows-embedded-mpv-runtime-pin
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
refresh:
|
||||||
|
name: Refresh checked-in runtime pin
|
||||||
|
if: github.repository == '4gray/iptvnator'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Refresh pin when it approaches upstream retention
|
||||||
|
id: refresh
|
||||||
|
env:
|
||||||
|
FORCE_REFRESH: ${{ inputs.force && 'true' || 'false' }}
|
||||||
|
GITHUB_TOKEN: ${{ github.token }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "${FORCE_REFRESH}" = "true" ]; then
|
||||||
|
node tools/embedded-mpv/update-windows-runtime-pin.mjs --force
|
||||||
|
else
|
||||||
|
node tools/embedded-mpv/update-windows-runtime-pin.mjs
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Validate updated pin
|
||||||
|
if: steps.refresh.outputs.changed == 'true'
|
||||||
|
run: >-
|
||||||
|
node --test
|
||||||
|
tools/embedded-mpv/windows-runtime-pin.test.mjs
|
||||||
|
|
||||||
|
- name: Create or update refresh pull request
|
||||||
|
if: steps.refresh.outputs.changed == 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.PAT }}
|
||||||
|
BRANCH_NAME: automation/windows-embedded-mpv-runtime-pin
|
||||||
|
RELEASE_TAG: ${{ steps.refresh.outputs.release-tag }}
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ -z "${GH_TOKEN}" ]; then
|
||||||
|
echo "::error::The PAT secret is required so the bot-created PR triggers normal CI."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
gh auth setup-git
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
git switch -C "${BRANCH_NAME}"
|
||||||
|
git add -- tools/embedded-mpv/windows-runtime-pin.json
|
||||||
|
git commit -m "ci(embedded-mpv): refresh Windows runtime pin"
|
||||||
|
git fetch origin "${BRANCH_NAME}:refs/remotes/origin/${BRANCH_NAME}" || true
|
||||||
|
git push --force-with-lease origin "HEAD:refs/heads/${BRANCH_NAME}"
|
||||||
|
|
||||||
|
PR_BODY="$(printf '%s\n\n%s\n%s\n%s' \
|
||||||
|
'Automated refresh of the checksum-pinned Windows Embedded MPV CI input.' \
|
||||||
|
"- Pins upstream release \`${RELEASE_TAG}\` in repository history." \
|
||||||
|
'- Keeps the binary on its upstream host; IPTVnator does not mirror it.' \
|
||||||
|
'- Preserves the explicit checksum/layout-only license-verification statement.')"
|
||||||
|
PR_NUMBER="$(gh pr list \
|
||||||
|
--base master \
|
||||||
|
--head "${BRANCH_NAME}" \
|
||||||
|
--state open \
|
||||||
|
--json number \
|
||||||
|
--jq '.[0].number // empty')"
|
||||||
|
|
||||||
|
if [ -n "${PR_NUMBER}" ]; then
|
||||||
|
gh pr edit "${PR_NUMBER}" \
|
||||||
|
--title "ci(embedded-mpv): refresh Windows runtime pin" \
|
||||||
|
--body "${PR_BODY}"
|
||||||
|
gh pr view "${PR_NUMBER}" --json url --jq '.url'
|
||||||
|
else
|
||||||
|
gh pr create \
|
||||||
|
--base master \
|
||||||
|
--head "${BRANCH_NAME}" \
|
||||||
|
--title "ci(embedded-mpv): refresh Windows runtime pin" \
|
||||||
|
--body "${PR_BODY}"
|
||||||
|
fi
|
||||||
@@ -496,6 +496,22 @@ Key files:
|
|||||||
holds its own blocker in `EmbeddedMpvNativeService`; external MPV/VLC
|
holds its own blocker in `EmbeddedMpvNativeService`; external MPV/VLC
|
||||||
inhibit the screensaver themselves.
|
inhibit the screensaver themselves.
|
||||||
|
|
||||||
|
## Windows Embedded MPV Pin Maintenance
|
||||||
|
|
||||||
|
- PR, master, and tag builds resolve the Windows runtime only from
|
||||||
|
`tools/embedded-mpv/windows-runtime-pin.json`; repository variables are not
|
||||||
|
build inputs.
|
||||||
|
- Validate the checked-in schema and provenance with
|
||||||
|
`pnpm embedded-mpv:windows-runtime-pin:check`.
|
||||||
|
- Prepare a manual rotation with
|
||||||
|
`pnpm embedded-mpv:windows-runtime-pin:refresh -- --force`. The weekly
|
||||||
|
`refresh-windows-embedded-mpv-runtime.yaml` workflow runs the same updater
|
||||||
|
and opens a reviewable PR before upstream retention expires.
|
||||||
|
- The PAT-backed refresh job must keep every third-party action pinned to a
|
||||||
|
full commit. Do not mirror the upstream binary without complete
|
||||||
|
corresponding source, build records, license notices, and a validated
|
||||||
|
transitive license closure.
|
||||||
|
|
||||||
## Linux Embedded MPV Packaging
|
## Linux Embedded MPV Packaging
|
||||||
|
|
||||||
- Official Linux frame-copy artifacts are x64-only. AppImage, DEB, RPM,
|
- Official Linux frame-copy artifacts are x64-only. AppImage, DEB, RPM,
|
||||||
|
|||||||
@@ -148,6 +148,22 @@ pnpm run make:app
|
|||||||
nx run electron-backend:make
|
nx run electron-backend:make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows Embedded MPV Pin Maintenance
|
||||||
|
|
||||||
|
- PR, master, and tag builds resolve the Windows runtime only from
|
||||||
|
`tools/embedded-mpv/windows-runtime-pin.json`; repository variables are not
|
||||||
|
build inputs.
|
||||||
|
- Validate the checked-in schema and provenance with
|
||||||
|
`pnpm embedded-mpv:windows-runtime-pin:check`.
|
||||||
|
- Prepare a manual rotation with
|
||||||
|
`pnpm embedded-mpv:windows-runtime-pin:refresh -- --force`. The weekly
|
||||||
|
`refresh-windows-embedded-mpv-runtime.yaml` workflow runs the same updater
|
||||||
|
and opens a reviewable PR before upstream retention expires.
|
||||||
|
- The PAT-backed refresh job must keep every third-party action pinned to a
|
||||||
|
full commit. Do not mirror the upstream binary without complete
|
||||||
|
corresponding source, build records, license notices, and a validated
|
||||||
|
transitive license closure.
|
||||||
|
|
||||||
### Electron CDP Debugging
|
### Electron CDP Debugging
|
||||||
|
|
||||||
- Start Electron in dev mode with: `nx serve electron-backend`
|
- Start Electron in dev mode with: `nx serve electron-backend`
|
||||||
|
|||||||
@@ -886,17 +886,35 @@ builder; both matrices reuse one YAML-anchored step list to prevent packaging
|
|||||||
logic drift. Draft release assembly still requires both matrices, so a public
|
logic drift. Draft release assembly still requires both matrices, so a public
|
||||||
release cannot silently omit a promised platform.
|
release cannot silently omit a promised platform.
|
||||||
|
|
||||||
Windows CI uses a checksum-pinned `win32-x64` runtime archive configured
|
Windows CI reads its single checksum-pinned `win32-x64` input from
|
||||||
through `IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL` and
|
`tools/embedded-mpv/windows-runtime-pin.json`. The validated pin owns the exact
|
||||||
`IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256`. Non-tag artifact builds have
|
upstream release, non-v3 `mpv-dev-lgpl-x86_64` asset URL, GitHub-published
|
||||||
a pinned `zhongfly/mpv-winbuild` `mpv-dev-lgpl-x86_64` fallback; tagged
|
SHA-256 digest, mpv commit, build-run evidence, retention policy, and the
|
||||||
releases require explicit repository configuration. Upstream retains only its
|
limited license-verification statement. PR, master, and tag builds all consume
|
||||||
latest 30 daily builds, so the fallback and any repository-variable copy must
|
that checked-in record; mutable repository variables cannot silently change a
|
||||||
be refreshed as one URL/checksum pair before expiry. A long-lived mirror must
|
build or drift from the cache key.
|
||||||
publish the matching source/build records and license notices with the binary.
|
|
||||||
The archive helper accepts normal `lib/` + `bin/` prefixes and common flat
|
`refresh-windows-embedded-mpv-runtime.yaml` checks the pin weekly. If its asset
|
||||||
archives, preserves the DLL basename encoded by the import library, and
|
is unavailable or 14 days old, the dependency-free updater selects the newest
|
||||||
generates minimal build metadata only when the archive lacks it.
|
matching public release with a GitHub digest and upstream build evidence,
|
||||||
|
verifies that it is downloadable, updates only the JSON pin, and opens or
|
||||||
|
refreshes a reviewable bot PR. The `PAT` repository secret is used deliberately
|
||||||
|
for that PR so its create/synchronize events trigger the normal CI workflows;
|
||||||
|
it needs repository contents and pull-request access. Run
|
||||||
|
`pnpm embedded-mpv:windows-runtime-pin:check` for a local schema check or
|
||||||
|
`pnpm embedded-mpv:windows-runtime-pin:refresh -- --force` to prepare the same
|
||||||
|
update manually.
|
||||||
|
|
||||||
|
The zhongfly asset contains only libmpv headers, its import library, and the
|
||||||
|
DLL. Upstream labels it LGPLv2.1+ with statically linked LGPLv3 FFmpeg, but also
|
||||||
|
states that its transitive LGPL compatibility is not guaranteed. IPTVnator
|
||||||
|
therefore verifies availability, checksum, and archive layout only, records
|
||||||
|
that limitation in the pin and generated runtime manifest, and does not host a
|
||||||
|
long-lived mirror of the binary. A future mirror or first-party Windows build
|
||||||
|
must first publish complete corresponding source, exact build scripts and
|
||||||
|
patches, license notices, and a validated transitive license closure beside the
|
||||||
|
binary. The archive helper accepts normal `lib/` + `bin/` prefixes and common
|
||||||
|
flat archives, and preserves the DLL basename encoded by the import library.
|
||||||
|
|
||||||
The Linux builder pins FFmpeg `8.1`, mpv `0.41.0`, libplacebo `7.360.1`,
|
The Linux builder pins FFmpeg `8.1`, mpv `0.41.0`, libplacebo `7.360.1`,
|
||||||
libass `0.17.3`, FreeType `2.13.3`, FriBidi `1.0.16`, HarfBuzz `8.5.0`,
|
libass `0.17.3`, FreeType `2.13.3`, FriBidi `1.0.16`, HarfBuzz `8.5.0`,
|
||||||
@@ -966,7 +984,16 @@ For tagged macOS builds, CI must:
|
|||||||
- set `IPTVNATOR_EMBEDDED_MPV_ARCH=${arch}` for backend build and packaging
|
- set `IPTVNATOR_EMBEDDED_MPV_ARCH=${arch}` for backend build and packaging
|
||||||
- set `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` for packaging and package-layout verification
|
- set `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` for packaging and package-layout verification
|
||||||
|
|
||||||
For Windows builds, CI must restore the `win32-x64` staged runtime cache or stage the checksum-pinned runtime archive before `pnpm run build:backend`. The Windows job must set `IPTVNATOR_EMBEDDED_MPV_PLATFORM=win32`, `IPTVNATOR_EMBEDDED_MPV_ARCH=x64`, and `IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` for backend build, package make, and package-layout verification. CI narrows `electron-builder.json` to x64 Windows targets while only a `win32-x64` runtime is available. The Windows job is pinned to `windows-2022` until the Electron `node-gyp` toolchain can identify Visual Studio 18 from `windows-latest`.
|
For Windows builds, CI must resolve the validated checked-in pin, restore its
|
||||||
|
exact-keyed `win32-x64` staged runtime cache or stage that checksum-pinned
|
||||||
|
archive, and only then run `pnpm run build:backend`. The Windows job must set
|
||||||
|
`IPTVNATOR_EMBEDDED_MPV_PLATFORM=win32`,
|
||||||
|
`IPTVNATOR_EMBEDDED_MPV_ARCH=x64`, and
|
||||||
|
`IPTVNATOR_REQUIRE_EMBEDDED_MPV=1` for backend build, package make, and
|
||||||
|
package-layout verification. CI narrows `electron-builder.json` to x64 Windows
|
||||||
|
targets while only a `win32-x64` runtime is available. The Windows job is
|
||||||
|
pinned to `windows-2022` until the Electron `node-gyp` toolchain can identify
|
||||||
|
Visual Studio 18 from `windows-latest`.
|
||||||
|
|
||||||
For Linux builds, CI first builds or restores the pinned x64 source runtime and
|
For Linux builds, CI first builds or restores the pinned x64 source runtime and
|
||||||
stages it under `vendor/embedded-mpv/linux-x64`. It then runs three isolated
|
stages it under `vendor/embedded-mpv/linux-x64`. It then runs three isolated
|
||||||
|
|||||||
@@ -61,6 +61,8 @@
|
|||||||
"embedded-mpv:stage-runtime": "node tools/embedded-mpv/stage-runtime.mjs",
|
"embedded-mpv:stage-runtime": "node tools/embedded-mpv/stage-runtime.mjs",
|
||||||
"embedded-mpv:stage-runtime:macos": "node tools/embedded-mpv/stage-macos-runtime.mjs",
|
"embedded-mpv:stage-runtime:macos": "node tools/embedded-mpv/stage-macos-runtime.mjs",
|
||||||
"embedded-mpv:stage-runtime:windows-archive": "node tools/embedded-mpv/stage-windows-runtime-archive.mjs",
|
"embedded-mpv:stage-runtime:windows-archive": "node tools/embedded-mpv/stage-windows-runtime-archive.mjs",
|
||||||
|
"embedded-mpv:windows-runtime-pin:check": "node tools/embedded-mpv/windows-runtime-pin.mjs",
|
||||||
|
"embedded-mpv:windows-runtime-pin:refresh": "node tools/embedded-mpv/update-windows-runtime-pin.mjs",
|
||||||
"serve:backend:embedded-mpv": "pnpm embedded-mpv:build-native:homebrew && IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1 IPTVNATOR_ENABLE_EMBEDDED_MPV_EXPERIMENT=1 pnpm serve:backend",
|
"serve:backend:embedded-mpv": "pnpm embedded-mpv:build-native:homebrew && IPTVNATOR_EMBEDDED_MPV_ALLOW_HOMEBREW=1 IPTVNATOR_ENABLE_EMBEDDED_MPV_EXPERIMENT=1 pnpm serve:backend",
|
||||||
"package:app": "nx run electron-backend:make --prepackageOnly",
|
"package:app": "nx run electron-backend:make --prepackageOnly",
|
||||||
"make:app": "nx run electron-backend:make",
|
"make:app": "nx run electron-backend:make",
|
||||||
|
|||||||
@@ -82,6 +82,31 @@ pnpm embedded-mpv:build-runtime:linux -- /tmp/linux-prefix
|
|||||||
pnpm embedded-mpv:stage-runtime -- linux x64 /tmp/linux-prefix
|
pnpm embedded-mpv:stage-runtime -- linux x64 /tmp/linux-prefix
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Windows CI pin lifecycle
|
||||||
|
|
||||||
|
Windows package builds consume the one validated record in
|
||||||
|
`windows-runtime-pin.json`; URL and checksum repository variables are not build
|
||||||
|
inputs. Check it locally with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm embedded-mpv:windows-runtime-pin:check
|
||||||
|
```
|
||||||
|
|
||||||
|
The weekly `refresh-windows-embedded-mpv-runtime.yaml` workflow opens a bot PR
|
||||||
|
when the upstream asset is unavailable or 14 days old, well before zhongfly's
|
||||||
|
30-day retention boundary. A manual refresh uses the same dependency-free
|
||||||
|
updater:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm embedded-mpv:windows-runtime-pin:refresh -- --force
|
||||||
|
```
|
||||||
|
|
||||||
|
The upstream archive is checksum- and layout-verified, not independently
|
||||||
|
certified as a complete LGPL closure. It contains no corresponding source or
|
||||||
|
license notices, so IPTVnator does not mirror it. Any future stable mirror must
|
||||||
|
ship complete corresponding source, exact build scripts and patches, notices,
|
||||||
|
and a validated transitive license record beside the binary.
|
||||||
|
|
||||||
The macOS builder verifies every downloaded archive against its pinned
|
The macOS builder verifies every downloaded archive against its pinned
|
||||||
SHA-256 digest before extraction. FreeType uses its official SourceForge
|
SHA-256 digest before extraction. FreeType uses its official SourceForge
|
||||||
distribution as the primary source and the official Savannah distribution as
|
distribution as the primary source and the official Savannah distribution as
|
||||||
@@ -398,14 +423,14 @@ scoped to asset selection/download. Candidate/stable
|
|||||||
promotion is manual after installed-Snap frame-copy and missing-runtime
|
promotion is manual after installed-Snap frame-copy and missing-runtime
|
||||||
fallback smoke; GitHub Actions never promotes automatically.
|
fallback smoke; GitHub Actions never promotes automatically.
|
||||||
|
|
||||||
Windows CI stages a checksum-pinned x64 LGPL archive. The DLL basename encoded
|
Windows CI stages the x64 LGPL archive selected by the checked-in validated pin
|
||||||
in its import library is preserved and must be present beside
|
described above. PR, master, and tag builds consume that same record; repository
|
||||||
`iptvnator_mpv_helper.exe`. Tagged releases require explicit repository
|
variables and fallback URLs are not build inputs. The DLL basename encoded in
|
||||||
configuration; the public fallback is for non-tag artifacts only. The upstream
|
the archive's import library is preserved and must be present beside
|
||||||
keeps only its latest 30 daily builds, so the fallback URL and checksum plus any
|
`iptvnator_mpv_helper.exe`. The weekly workflow rotates the reviewed pin before
|
||||||
matching repository variables must be refreshed as one pair before they age
|
the upstream's latest-30-build retention removes it. A permanent mirror still
|
||||||
out. A permanent mirror must publish the corresponding source/build records and
|
requires the complete corresponding source/build records, license notices, and
|
||||||
license notices with the binary.
|
validated transitive license closure beside the binary.
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import path from 'path';
|
|||||||
import { spawnSync } from 'child_process';
|
import { spawnSync } from 'child_process';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import { pipeline } from 'stream/promises';
|
import { pipeline } from 'stream/promises';
|
||||||
|
import { readWindowsRuntimePin } from './windows-runtime-pin.mjs';
|
||||||
|
|
||||||
const rawArgs = process.argv.slice(2);
|
const rawArgs = process.argv.slice(2);
|
||||||
const args = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
|
const args = rawArgs[0] === '--' ? rawArgs.slice(1) : rawArgs;
|
||||||
@@ -18,7 +19,7 @@ if (!archiveSource || !rawExpectedSha256) {
|
|||||||
[
|
[
|
||||||
'Usage: node tools/embedded-mpv/stage-windows-runtime-archive.mjs <archive-url-or-path> <sha256>',
|
'Usage: node tools/embedded-mpv/stage-windows-runtime-archive.mjs <archive-url-or-path> <sha256>',
|
||||||
'',
|
'',
|
||||||
'Downloads or reads a checksum-pinned LGPL-compatible Windows libmpv runtime archive,',
|
'Downloads or reads a checksum-pinned, upstream-labelled LGPL Windows libmpv runtime archive,',
|
||||||
'extracts it, and stages it as vendor/embedded-mpv/win32-x64.',
|
'extracts it, and stages it as vendor/embedded-mpv/win32-x64.',
|
||||||
'',
|
'',
|
||||||
'The archive must contain a prefix with:',
|
'The archive must contain a prefix with:',
|
||||||
@@ -189,23 +190,50 @@ function copyFile(sourcePath, destinationPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function writeGeneratedManifest(destinationPath, archiveSha256) {
|
function writeGeneratedManifest(destinationPath, archiveSha256) {
|
||||||
|
let checkedInPin = null;
|
||||||
|
try {
|
||||||
|
const candidate = readWindowsRuntimePin();
|
||||||
|
if (
|
||||||
|
candidate.asset.url === archiveSource &&
|
||||||
|
candidate.asset.sha256 === archiveSha256
|
||||||
|
) {
|
||||||
|
checkedInPin = candidate;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Arbitrary local archives remain supported without repository pin metadata.
|
||||||
|
}
|
||||||
|
const upstreamEvidence = checkedInPin?.upstream;
|
||||||
const manifest = {
|
const manifest = {
|
||||||
sourceDistribution: archiveSource,
|
sourceDistribution: archiveSource,
|
||||||
archive: {
|
archive: {
|
||||||
urlOrPath: archiveSource,
|
urlOrPath: archiveSource,
|
||||||
sha256: archiveSha256,
|
sha256: archiveSha256,
|
||||||
},
|
},
|
||||||
|
verification: {
|
||||||
|
level: 'checksum-and-layout-only',
|
||||||
|
licenseClosure: 'not-independently-verified',
|
||||||
|
},
|
||||||
|
...(upstreamEvidence
|
||||||
|
? {
|
||||||
|
upstream: {
|
||||||
|
releaseTag: checkedInPin.releaseTag,
|
||||||
|
mpvCommit: upstreamEvidence.mpvCommit,
|
||||||
|
buildRunUrl: upstreamEvidence.buildRunUrl,
|
||||||
|
licenseClaim: upstreamEvidence.licenseClaim,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
ffmpeg: {
|
ffmpeg: {
|
||||||
licensePolicy:
|
licensePolicy:
|
||||||
'LGPL-compatible Windows runtime archive supplied to CI.',
|
'The upstream archive describes statically linked FFmpeg as LGPLv3; IPTVnator does not independently certify its complete static dependency closure.',
|
||||||
configureFlags:
|
configureFlags:
|
||||||
'Record exact FFmpeg configure flags in the upstream runtime manifest when available.',
|
'Not embedded in the upstream binary archive; consult the pinned upstream build run.',
|
||||||
},
|
},
|
||||||
mpv: {
|
mpv: {
|
||||||
licensePolicy:
|
licensePolicy:
|
||||||
'LGPL-compatible libmpv Windows runtime archive supplied to CI.',
|
'The upstream archive describes libmpv as LGPLv2.1+ with GPL-only features disabled; IPTVnator verifies only the checksum and required files.',
|
||||||
mesonFlags:
|
mesonFlags:
|
||||||
'Record exact mpv Meson flags in the upstream runtime manifest when available.',
|
'Not embedded in the upstream binary archive; consult the pinned upstream build run.',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import {
|
||||||
|
readWindowsRuntimePin,
|
||||||
|
serializeWindowsRuntimePin,
|
||||||
|
validateWindowsRuntimePin,
|
||||||
|
WINDOWS_RUNTIME_LICENSE_CLAIM,
|
||||||
|
WINDOWS_RUNTIME_PIN_PATH,
|
||||||
|
WINDOWS_RUNTIME_REPOSITORY,
|
||||||
|
} from './windows-runtime-pin.mjs';
|
||||||
|
|
||||||
|
const modulePath = fileURLToPath(import.meta.url);
|
||||||
|
const RELEASES_API = `https://api.github.com/repos/${WINDOWS_RUNTIME_REPOSITORY}/releases?per_page=50`;
|
||||||
|
const ASSET_NAME_PATTERN = /^mpv-dev-lgpl-x86_64-\d{8}-git-[a-f0-9]{10}\.7z$/;
|
||||||
|
|
||||||
|
export const WINDOWS_RUNTIME_REFRESH_AFTER_DAYS = 14;
|
||||||
|
|
||||||
|
function githubApiHeaders() {
|
||||||
|
return {
|
||||||
|
Accept: 'application/vnd.github+json',
|
||||||
|
'User-Agent': 'iptvnator-windows-runtime-pin-updater',
|
||||||
|
...(process.env.GITHUB_TOKEN
|
||||||
|
? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchJson(url, fetchImpl) {
|
||||||
|
const response = await fetchImpl(url, { headers: githubApiHeaders() });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to query Windows runtime releases: ${response.status} ${response.statusText}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractUpstreamEvidence(body) {
|
||||||
|
const mpvCommit = body?.match(
|
||||||
|
/github\.com\/mpv-player\/mpv\/commit\/([a-f0-9]{40})/i
|
||||||
|
)?.[1];
|
||||||
|
const buildRunId = body?.match(
|
||||||
|
/github\.com\/zhongfly\/mpv-winbuild\/actions\/runs\/(\d+)/i
|
||||||
|
)?.[1];
|
||||||
|
if (!mpvCommit || !buildRunId) {
|
||||||
|
throw new Error(
|
||||||
|
'Latest Windows runtime release lacks the expected mpv commit or build-run evidence.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mpvCommit: mpvCommit.toLowerCase(),
|
||||||
|
buildRunUrl: `https://github.com/zhongfly/mpv-winbuild/actions/runs/${buildRunId}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pinFromUpstreamRelease(release) {
|
||||||
|
if (release?.draft || release?.prerelease) {
|
||||||
|
throw new Error(
|
||||||
|
'Windows runtime pin cannot use a draft or prerelease.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const matchingAssets = (release?.assets ?? []).filter((asset) =>
|
||||||
|
ASSET_NAME_PATTERN.test(asset?.name ?? '')
|
||||||
|
);
|
||||||
|
if (matchingAssets.length !== 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Release ${release?.tag_name ?? '<unknown>'} must contain exactly one non-v3 x86_64 LGPL dev archive.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const asset = matchingAssets[0];
|
||||||
|
const sha256 = asset.digest?.match(/^sha256:([a-f0-9]{64})$/)?.[1];
|
||||||
|
if (!sha256) {
|
||||||
|
throw new Error(
|
||||||
|
`Release asset ${asset.name} must expose a GitHub SHA-256 digest.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const evidence = extractUpstreamEvidence(release.body);
|
||||||
|
return validateWindowsRuntimePin({
|
||||||
|
schemaVersion: 1,
|
||||||
|
repository: WINDOWS_RUNTIME_REPOSITORY,
|
||||||
|
releaseTag: release.tag_name,
|
||||||
|
publishedAt: release.published_at,
|
||||||
|
retentionDays: 30,
|
||||||
|
asset: {
|
||||||
|
name: asset.name,
|
||||||
|
url: asset.browser_download_url,
|
||||||
|
sha256,
|
||||||
|
},
|
||||||
|
upstream: {
|
||||||
|
...evidence,
|
||||||
|
licenseClaim: WINDOWS_RUNTIME_LICENSE_CLAIM,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectNewestWindowsRuntimePin(releases) {
|
||||||
|
if (!Array.isArray(releases)) {
|
||||||
|
throw new Error('Windows runtime releases response must be an array.');
|
||||||
|
}
|
||||||
|
const candidates = releases
|
||||||
|
.filter((release) => !release?.draft && !release?.prerelease)
|
||||||
|
.filter((release) =>
|
||||||
|
(release?.assets ?? []).some((asset) =>
|
||||||
|
ASSET_NAME_PATTERN.test(asset?.name ?? '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.sort(
|
||||||
|
(left, right) =>
|
||||||
|
Date.parse(right.published_at) - Date.parse(left.published_at)
|
||||||
|
);
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
throw new Error('No suitable Windows LGPL runtime release was found.');
|
||||||
|
}
|
||||||
|
return pinFromUpstreamRelease(candidates[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runtimePinAgeDays(pin, now = new Date()) {
|
||||||
|
return (now.getTime() - Date.parse(pin.publishedAt)) / 86_400_000;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function isRuntimeAssetAvailable(url, fetchImpl = fetch) {
|
||||||
|
try {
|
||||||
|
const response = await fetchImpl(url, {
|
||||||
|
method: 'HEAD',
|
||||||
|
redirect: 'follow',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'iptvnator-windows-runtime-pin-updater',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshWindowsRuntimePin({
|
||||||
|
pinPath = WINDOWS_RUNTIME_PIN_PATH,
|
||||||
|
now = new Date(),
|
||||||
|
force = false,
|
||||||
|
dryRun = false,
|
||||||
|
fetchImpl = fetch,
|
||||||
|
} = {}) {
|
||||||
|
const current = readWindowsRuntimePin(pinPath);
|
||||||
|
const currentAvailable = await isRuntimeAssetAvailable(
|
||||||
|
current.asset.url,
|
||||||
|
fetchImpl
|
||||||
|
);
|
||||||
|
const ageDays = runtimePinAgeDays(current, now);
|
||||||
|
const refreshReason = force
|
||||||
|
? 'forced'
|
||||||
|
: !currentAvailable
|
||||||
|
? 'unavailable'
|
||||||
|
: ageDays >= WINDOWS_RUNTIME_REFRESH_AFTER_DAYS
|
||||||
|
? 'age-threshold'
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!refreshReason) {
|
||||||
|
return {
|
||||||
|
changed: false,
|
||||||
|
reason: 'current',
|
||||||
|
current,
|
||||||
|
next: current,
|
||||||
|
ageDays,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const releases = await fetchJson(RELEASES_API, fetchImpl);
|
||||||
|
const next = selectNewestWindowsRuntimePin(releases);
|
||||||
|
if (!(await isRuntimeAssetAvailable(next.asset.url, fetchImpl))) {
|
||||||
|
throw new Error(
|
||||||
|
`Selected Windows runtime asset is not downloadable: ${next.asset.url}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const changed =
|
||||||
|
serializeWindowsRuntimePin(next) !==
|
||||||
|
serializeWindowsRuntimePin(current);
|
||||||
|
if (changed && !dryRun) {
|
||||||
|
fs.writeFileSync(pinPath, serializeWindowsRuntimePin(next));
|
||||||
|
}
|
||||||
|
return { changed, reason: refreshReason, current, next, ageDays };
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendGitHubOutputs(result) {
|
||||||
|
if (!process.env.GITHUB_OUTPUT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs.appendFileSync(
|
||||||
|
process.env.GITHUB_OUTPUT,
|
||||||
|
[
|
||||||
|
`changed=${result.changed}`,
|
||||||
|
`reason=${result.reason}`,
|
||||||
|
`release-tag=${result.next.releaseTag}`,
|
||||||
|
'',
|
||||||
|
].join('\n')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const result = await refreshWindowsRuntimePin({
|
||||||
|
force: process.argv.includes('--force'),
|
||||||
|
dryRun: process.argv.includes('--dry-run'),
|
||||||
|
});
|
||||||
|
appendGitHubOutputs(result);
|
||||||
|
const action = result.changed ? 'updated' : 'kept';
|
||||||
|
console.log(
|
||||||
|
`Windows Embedded MPV runtime pin ${action}: ${result.next.releaseTag} (${result.reason}).`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] === modulePath) {
|
||||||
|
main().catch((error) => {
|
||||||
|
console.error(error instanceof Error ? error.message : String(error));
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"repository": "zhongfly/mpv-winbuild",
|
||||||
|
"releaseTag": "2026-08-21-49418246f3",
|
||||||
|
"publishedAt": "2026-08-21T12:20:41Z",
|
||||||
|
"retentionDays": 30,
|
||||||
|
"asset": {
|
||||||
|
"name": "mpv-dev-lgpl-x86_64-20260821-git-49418246f3.7z",
|
||||||
|
"url": "https://github.com/zhongfly/mpv-winbuild/releases/download/2026-08-21-49418246f3/mpv-dev-lgpl-x86_64-20260821-git-49418246f3.7z",
|
||||||
|
"sha256": "317dfd9ee814be76e5f6e20b45efcc07440389a62b55dd85201829b4880510e0"
|
||||||
|
},
|
||||||
|
"upstream": {
|
||||||
|
"mpvCommit": "49418246f30a9c24af31ac184aa24f39755db89a",
|
||||||
|
"buildRunUrl": "https://github.com/zhongfly/mpv-winbuild/actions/runs/32479875364",
|
||||||
|
"licenseClaim": "Upstream labels this libmpv build LGPLv2.1+ with statically linked LGPLv3 FFmpeg; IPTVnator verifies the checksum and archive layout, not the complete transitive license closure."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const modulePath = fileURLToPath(import.meta.url);
|
||||||
|
const moduleDir = path.dirname(modulePath);
|
||||||
|
|
||||||
|
export const WINDOWS_RUNTIME_PIN_PATH = path.join(
|
||||||
|
moduleDir,
|
||||||
|
'windows-runtime-pin.json'
|
||||||
|
);
|
||||||
|
export const WINDOWS_RUNTIME_REPOSITORY = 'zhongfly/mpv-winbuild';
|
||||||
|
export const WINDOWS_RUNTIME_LICENSE_CLAIM =
|
||||||
|
'Upstream labels this libmpv build LGPLv2.1+ with statically linked LGPLv3 FFmpeg; IPTVnator verifies the checksum and archive layout, not the complete transitive license closure.';
|
||||||
|
|
||||||
|
const RELEASE_TAG_PATTERN = /^(\d{4})-(\d{2})-(\d{2})-([a-f0-9]{10})$/;
|
||||||
|
const ASSET_NAME_PATTERN =
|
||||||
|
/^mpv-dev-lgpl-x86_64-(\d{8})-git-([a-f0-9]{10})\.7z$/;
|
||||||
|
const SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
||||||
|
const COMMIT_PATTERN = /^[a-f0-9]{40}$/;
|
||||||
|
const BUILD_RUN_PATTERN =
|
||||||
|
/^https:\/\/github\.com\/zhongfly\/mpv-winbuild\/actions\/runs\/(\d+)$/;
|
||||||
|
|
||||||
|
function hasExactFields(value, fields) {
|
||||||
|
return (
|
||||||
|
value !== null &&
|
||||||
|
typeof value === 'object' &&
|
||||||
|
!Array.isArray(value) &&
|
||||||
|
Object.keys(value).sort().join('\0') === [...fields].sort().join('\0')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(`Invalid Windows Embedded MPV runtime pin: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectedAssetUrl(releaseTag, assetName) {
|
||||||
|
return `https://github.com/${WINDOWS_RUNTIME_REPOSITORY}/releases/download/${releaseTag}/${assetName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateWindowsRuntimePin(pin) {
|
||||||
|
assert(
|
||||||
|
hasExactFields(pin, [
|
||||||
|
'schemaVersion',
|
||||||
|
'repository',
|
||||||
|
'releaseTag',
|
||||||
|
'publishedAt',
|
||||||
|
'retentionDays',
|
||||||
|
'asset',
|
||||||
|
'upstream',
|
||||||
|
]),
|
||||||
|
'root fields do not match schema version 1'
|
||||||
|
);
|
||||||
|
assert(pin.schemaVersion === 1, 'schemaVersion must be 1');
|
||||||
|
assert(
|
||||||
|
pin.repository === WINDOWS_RUNTIME_REPOSITORY,
|
||||||
|
`repository must be ${WINDOWS_RUNTIME_REPOSITORY}`
|
||||||
|
);
|
||||||
|
assert(pin.retentionDays === 30, 'retentionDays must be 30');
|
||||||
|
|
||||||
|
const releaseMatch = pin.releaseTag?.match(RELEASE_TAG_PATTERN);
|
||||||
|
assert(releaseMatch, 'releaseTag has an unexpected format');
|
||||||
|
const publishedAt = Date.parse(pin.publishedAt);
|
||||||
|
assert(
|
||||||
|
Number.isFinite(publishedAt),
|
||||||
|
'publishedAt must be an ISO timestamp'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
hasExactFields(pin.asset, ['name', 'url', 'sha256']),
|
||||||
|
'asset fields do not match schema version 1'
|
||||||
|
);
|
||||||
|
const assetMatch = pin.asset.name?.match(ASSET_NAME_PATTERN);
|
||||||
|
assert(assetMatch, 'asset.name must be the non-v3 x86_64 LGPL dev archive');
|
||||||
|
assert(
|
||||||
|
assetMatch[1] === releaseMatch.slice(1, 4).join(''),
|
||||||
|
'asset date must match the release tag date'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
assetMatch[2] === releaseMatch[4],
|
||||||
|
'asset commit suffix must match the release tag'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
pin.asset.url === expectedAssetUrl(pin.releaseTag, pin.asset.name),
|
||||||
|
'asset.url must be derived from the pinned release and asset name'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
SHA256_PATTERN.test(pin.asset.sha256),
|
||||||
|
'asset.sha256 must be a lowercase SHA-256 digest'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
hasExactFields(pin.upstream, [
|
||||||
|
'mpvCommit',
|
||||||
|
'buildRunUrl',
|
||||||
|
'licenseClaim',
|
||||||
|
]),
|
||||||
|
'upstream fields do not match schema version 1'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
COMMIT_PATTERN.test(pin.upstream.mpvCommit),
|
||||||
|
'upstream.mpvCommit must be a full commit hash'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
pin.upstream.mpvCommit.startsWith(releaseMatch[4]),
|
||||||
|
'upstream.mpvCommit must match the release tag suffix'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
BUILD_RUN_PATTERN.test(pin.upstream.buildRunUrl),
|
||||||
|
'upstream.buildRunUrl must identify the zhongfly build run'
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
pin.upstream.licenseClaim === WINDOWS_RUNTIME_LICENSE_CLAIM,
|
||||||
|
'upstream.licenseClaim must preserve the limited verification statement'
|
||||||
|
);
|
||||||
|
|
||||||
|
return pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readWindowsRuntimePin(pinPath = WINDOWS_RUNTIME_PIN_PATH) {
|
||||||
|
return validateWindowsRuntimePin(
|
||||||
|
JSON.parse(fs.readFileSync(pinPath, 'utf8'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeWindowsRuntimePin(pin) {
|
||||||
|
validateWindowsRuntimePin(pin);
|
||||||
|
return `${JSON.stringify(pin, null, 4)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function appendWindowsRuntimeGitHubOutputs(
|
||||||
|
pin,
|
||||||
|
outputPath = process.env.GITHUB_OUTPUT
|
||||||
|
) {
|
||||||
|
validateWindowsRuntimePin(pin);
|
||||||
|
assert(outputPath, 'GITHUB_OUTPUT is required for --github-output');
|
||||||
|
fs.appendFileSync(
|
||||||
|
outputPath,
|
||||||
|
[
|
||||||
|
`url=${pin.asset.url}`,
|
||||||
|
`sha256=${pin.asset.sha256}`,
|
||||||
|
`asset-name=${pin.asset.name}`,
|
||||||
|
`release-tag=${pin.releaseTag}`,
|
||||||
|
`published-at=${pin.publishedAt}`,
|
||||||
|
'',
|
||||||
|
].join('\n')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const pin = readWindowsRuntimePin();
|
||||||
|
if (process.argv.includes('--github-output')) {
|
||||||
|
appendWindowsRuntimeGitHubOutputs(pin);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`Windows Embedded MPV runtime pin: ${pin.releaseTag} (${pin.asset.sha256})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] === modulePath) {
|
||||||
|
try {
|
||||||
|
main();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error instanceof Error ? error.message : String(error));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
import test from 'node:test';
|
||||||
|
import {
|
||||||
|
appendWindowsRuntimeGitHubOutputs,
|
||||||
|
readWindowsRuntimePin,
|
||||||
|
serializeWindowsRuntimePin,
|
||||||
|
validateWindowsRuntimePin,
|
||||||
|
WINDOWS_RUNTIME_LICENSE_CLAIM,
|
||||||
|
WINDOWS_RUNTIME_PIN_PATH,
|
||||||
|
} from './windows-runtime-pin.mjs';
|
||||||
|
import {
|
||||||
|
pinFromUpstreamRelease,
|
||||||
|
refreshWindowsRuntimePin,
|
||||||
|
runtimePinAgeDays,
|
||||||
|
selectNewestWindowsRuntimePin,
|
||||||
|
WINDOWS_RUNTIME_REFRESH_AFTER_DAYS,
|
||||||
|
} from './update-windows-runtime-pin.mjs';
|
||||||
|
|
||||||
|
const CURRENT_PIN = readWindowsRuntimePin();
|
||||||
|
|
||||||
|
function releaseFixture({
|
||||||
|
date = '2026-08-28',
|
||||||
|
commit = 'e8673660ab123456789012345678901234567890',
|
||||||
|
digest = '470437b5dc9f8c74092fdfab668e89bedf7b1a6385a53ffadf241a6a7a4c6ffb',
|
||||||
|
runId = '33215046953',
|
||||||
|
publishedAt = `${date}T22:32:29Z`,
|
||||||
|
} = {}) {
|
||||||
|
const compactDate = date.replaceAll('-', '');
|
||||||
|
const shortCommit = commit.slice(0, 10);
|
||||||
|
const tag = `${date}-${shortCommit}`;
|
||||||
|
const name = `mpv-dev-lgpl-x86_64-${compactDate}-git-${shortCommit}.7z`;
|
||||||
|
return {
|
||||||
|
draft: false,
|
||||||
|
prerelease: false,
|
||||||
|
tag_name: tag,
|
||||||
|
published_at: publishedAt,
|
||||||
|
body: [
|
||||||
|
`MPV Git commit: https://github.com/mpv-player/mpv/commit/${commit}`,
|
||||||
|
`Build Details: https://github.com/zhongfly/mpv-winbuild/actions/runs/${runId}`,
|
||||||
|
].join('\n'),
|
||||||
|
assets: [
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
digest: `sha256:${digest}`,
|
||||||
|
browser_download_url: `https://github.com/zhongfly/mpv-winbuild/releases/download/${tag}/${name}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: `mpv-dev-lgpl-x86_64-v3-${compactDate}-git-${shortCommit}.7z`,
|
||||||
|
digest: `sha256:${'a'.repeat(64)}`,
|
||||||
|
browser_download_url: 'https://example.invalid/v3.7z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function response({ ok = true, status = 200, json } = {}) {
|
||||||
|
return {
|
||||||
|
ok,
|
||||||
|
status,
|
||||||
|
statusText: ok ? 'OK' : 'Not Found',
|
||||||
|
json: async () => json,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPinFixture() {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'impv-win-pin-'));
|
||||||
|
const pinPath = path.join(root, 'windows-runtime-pin.json');
|
||||||
|
fs.writeFileSync(pinPath, serializeWindowsRuntimePin(CURRENT_PIN));
|
||||||
|
return { root, pinPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('checked-in Windows runtime pin is internally consistent', () => {
|
||||||
|
assert.equal(WINDOWS_RUNTIME_PIN_PATH.endsWith('.json'), true);
|
||||||
|
assert.match(CURRENT_PIN.asset.sha256, /^[a-f0-9]{64}$/);
|
||||||
|
assert.match(CURRENT_PIN.asset.name, /^mpv-dev-lgpl-x86_64-/);
|
||||||
|
assert.doesNotMatch(CURRENT_PIN.asset.name, /-v3-/);
|
||||||
|
assert.equal(
|
||||||
|
CURRENT_PIN.upstream.licenseClaim,
|
||||||
|
WINDOWS_RUNTIME_LICENSE_CLAIM
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pin validation rejects a softened license-verification statement', () => {
|
||||||
|
const invalid = structuredClone(CURRENT_PIN);
|
||||||
|
invalid.upstream.licenseClaim = 'Verified LGPL runtime.';
|
||||||
|
assert.throws(
|
||||||
|
() => validateWindowsRuntimePin(invalid),
|
||||||
|
/limited verification statement/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('upstream release selection excludes the v3 archive and keeps evidence', () => {
|
||||||
|
const older = releaseFixture({
|
||||||
|
date: '2026-08-27',
|
||||||
|
commit: '182fa6ca49123456789012345678901234567890',
|
||||||
|
publishedAt: '2026-08-27T12:41:23Z',
|
||||||
|
});
|
||||||
|
const latest = releaseFixture();
|
||||||
|
const pin = selectNewestWindowsRuntimePin([older, latest]);
|
||||||
|
|
||||||
|
assert.equal(pin.releaseTag, latest.tag_name);
|
||||||
|
assert.equal(pin.asset.name, latest.assets[0].name);
|
||||||
|
assert.equal(pin.asset.sha256, latest.assets[0].digest.slice(7));
|
||||||
|
assert.equal(pin.upstream.mpvCommit, latest.body.match(/[a-f0-9]{40}/)[0]);
|
||||||
|
assert.doesNotMatch(pin.asset.name, /-v3-/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('upstream release must provide GitHub digest and build evidence', () => {
|
||||||
|
const missingDigest = releaseFixture();
|
||||||
|
delete missingDigest.assets[0].digest;
|
||||||
|
assert.throws(
|
||||||
|
() => pinFromUpstreamRelease(missingDigest),
|
||||||
|
/must expose a GitHub SHA-256 digest/
|
||||||
|
);
|
||||||
|
|
||||||
|
const missingEvidence = releaseFixture();
|
||||||
|
missingEvidence.body = '';
|
||||||
|
assert.throws(
|
||||||
|
() => pinFromUpstreamRelease(missingEvidence),
|
||||||
|
/lacks the expected mpv commit or build-run evidence/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('young available pin does not query releases or rewrite the file', async () => {
|
||||||
|
const fixture = createPinFixture();
|
||||||
|
let requestCount = 0;
|
||||||
|
try {
|
||||||
|
const result = await refreshWindowsRuntimePin({
|
||||||
|
pinPath: fixture.pinPath,
|
||||||
|
now: new Date('2026-08-29T00:00:00Z'),
|
||||||
|
fetchImpl: async () => {
|
||||||
|
requestCount += 1;
|
||||||
|
return response();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(result.changed, false);
|
||||||
|
assert.equal(result.reason, 'current');
|
||||||
|
assert.equal(requestCount, 1);
|
||||||
|
assert.equal(
|
||||||
|
fs.readFileSync(fixture.pinPath, 'utf8'),
|
||||||
|
serializeWindowsRuntimePin(CURRENT_PIN)
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unavailable pin rotates to the newest downloadable release', async () => {
|
||||||
|
const fixture = createPinFixture();
|
||||||
|
const latest = releaseFixture();
|
||||||
|
const requests = [];
|
||||||
|
try {
|
||||||
|
const result = await refreshWindowsRuntimePin({
|
||||||
|
pinPath: fixture.pinPath,
|
||||||
|
now: new Date('2026-08-29T00:00:00Z'),
|
||||||
|
fetchImpl: async (url, options = {}) => {
|
||||||
|
requests.push([url, options.method ?? 'GET']);
|
||||||
|
if (url === CURRENT_PIN.asset.url) {
|
||||||
|
return response({ ok: false, status: 404 });
|
||||||
|
}
|
||||||
|
if (url.startsWith('https://api.github.com/')) {
|
||||||
|
return response({ json: [latest] });
|
||||||
|
}
|
||||||
|
return response();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(result.changed, true);
|
||||||
|
assert.equal(result.reason, 'unavailable');
|
||||||
|
assert.equal(
|
||||||
|
readWindowsRuntimePin(fixture.pinPath).releaseTag,
|
||||||
|
latest.tag_name
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
requests.map((request) => request[1]),
|
||||||
|
['HEAD', 'GET', 'HEAD']
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('age threshold rotates an available pin before upstream retention', async () => {
|
||||||
|
const fixture = createPinFixture();
|
||||||
|
const latest = releaseFixture();
|
||||||
|
try {
|
||||||
|
const result = await refreshWindowsRuntimePin({
|
||||||
|
pinPath: fixture.pinPath,
|
||||||
|
now: new Date('2026-09-05T13:00:00Z'),
|
||||||
|
fetchImpl: async (url) =>
|
||||||
|
url.startsWith('https://api.github.com/')
|
||||||
|
? response({ json: [latest] })
|
||||||
|
: response(),
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
runtimePinAgeDays(CURRENT_PIN, new Date('2026-09-05T13:00:00Z')) >=
|
||||||
|
WINDOWS_RUNTIME_REFRESH_AFTER_DAYS,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
assert.equal(result.changed, true);
|
||||||
|
assert.equal(result.reason, 'age-threshold');
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GitHub output exposes only the validated checked-in pin', () => {
|
||||||
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'impv-win-output-'));
|
||||||
|
const outputPath = path.join(root, 'output');
|
||||||
|
try {
|
||||||
|
appendWindowsRuntimeGitHubOutputs(CURRENT_PIN, outputPath);
|
||||||
|
const output = fs.readFileSync(outputPath, 'utf8');
|
||||||
|
assert.match(output, new RegExp(`sha256=${CURRENT_PIN.asset.sha256}`));
|
||||||
|
assert.match(
|
||||||
|
output,
|
||||||
|
new RegExp(`release-tag=${CURRENT_PIN.releaseTag}`)
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -5,6 +5,7 @@ import os from 'node:os';
|
|||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { readWindowsRuntimePin } from '../embedded-mpv/windows-runtime-pin.mjs';
|
||||||
|
|
||||||
const currentDir = dirname(fileURLToPath(import.meta.url));
|
const currentDir = dirname(fileURLToPath(import.meta.url));
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
@@ -15,6 +16,18 @@ const buildAndMakeWorkflow = fs.readFileSync(
|
|||||||
join(currentDir, '..', '..', '.github', 'workflows', 'build-and-make.yaml'),
|
join(currentDir, '..', '..', '.github', 'workflows', 'build-and-make.yaml'),
|
||||||
'utf8'
|
'utf8'
|
||||||
);
|
);
|
||||||
|
const windowsRuntimeRefreshWorkflow = fs.readFileSync(
|
||||||
|
join(
|
||||||
|
currentDir,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'.github',
|
||||||
|
'workflows',
|
||||||
|
'refresh-windows-embedded-mpv-runtime.yaml'
|
||||||
|
),
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const windowsRuntimePin = readWindowsRuntimePin();
|
||||||
const electronBuilderConfig = JSON.parse(
|
const electronBuilderConfig = JSON.parse(
|
||||||
fs.readFileSync(
|
fs.readFileSync(
|
||||||
join(currentDir, '..', '..', 'electron-builder.json'),
|
join(currentDir, '..', '..', 'electron-builder.json'),
|
||||||
@@ -874,16 +887,6 @@ test('Windows CI packages embedded MPV from a staged x64 runtime', () => {
|
|||||||
const requireEmbeddedMpvLines = buildAndMakeWorkflow
|
const requireEmbeddedMpvLines = buildAndMakeWorkflow
|
||||||
.split(/\r?\n/)
|
.split(/\r?\n/)
|
||||||
.filter((line) => line.includes('IPTVNATOR_REQUIRE_EMBEDDED_MPV:'));
|
.filter((line) => line.includes('IPTVNATOR_REQUIRE_EMBEDDED_MPV:'));
|
||||||
const defaultRuntimeUrls = [
|
|
||||||
...buildAndMakeWorkflow.matchAll(
|
|
||||||
/IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_URL:\s+(\S+)/g
|
|
||||||
),
|
|
||||||
].map((match) => match[1]);
|
|
||||||
const defaultRuntimeSha256s = [
|
|
||||||
...buildAndMakeWorkflow.matchAll(
|
|
||||||
/IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256:\s+([a-f0-9]{64})/g
|
|
||||||
),
|
|
||||||
].map((match) => match[1]);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
packageMetadata.scripts?.['embedded-mpv:stage-runtime:windows-archive'],
|
packageMetadata.scripts?.['embedded-mpv:stage-runtime:windows-archive'],
|
||||||
@@ -896,27 +899,61 @@ test('Windows CI packages embedded MPV from a staged x64 runtime', () => {
|
|||||||
assert.match(buildAndMakeWorkflow, /runner:\s+windows-2022/);
|
assert.match(buildAndMakeWorkflow, /runner:\s+windows-2022/);
|
||||||
assert.match(
|
assert.match(
|
||||||
buildAndMakeWorkflow,
|
buildAndMakeWorkflow,
|
||||||
/IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_URL/
|
/name:\s+Resolve pinned Windows Embedded MPV runtime/
|
||||||
);
|
);
|
||||||
assert.match(
|
assert.match(
|
||||||
buildAndMakeWorkflow,
|
buildAndMakeWorkflow,
|
||||||
/IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME_SHA256/
|
/node tools\/embedded-mpv\/windows-runtime-pin\.mjs --github-output/
|
||||||
);
|
);
|
||||||
assert.match(
|
assert.match(
|
||||||
buildAndMakeWorkflow,
|
buildAndMakeWorkflow,
|
||||||
/IPTVNATOR_DEFAULT_WINDOWS_EMBEDDED_MPV_RUNTIME_URL: https:\/\/github\.com\/zhongfly\/mpv-winbuild\/releases\/download\//
|
/WINDOWS_RUNTIME_URL:\s+\$\{\{ steps\.windows-embedded-mpv-runtime-pin\.outputs\.url \}\}/
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.match(
|
||||||
[...new Set(defaultRuntimeUrls)],
|
buildAndMakeWorkflow,
|
||||||
[
|
/WINDOWS_RUNTIME_SHA256:\s+\$\{\{ steps\.windows-embedded-mpv-runtime-pin\.outputs\.sha256 \}\}/
|
||||||
'https://github.com/zhongfly/mpv-winbuild/releases/download/2026-07-17-94335ab87a/mpv-dev-lgpl-x86_64-20260717-git-94335ab87a.7z',
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
assert.deepEqual(
|
assert.doesNotMatch(
|
||||||
[...new Set(defaultRuntimeSha256s)],
|
buildAndMakeWorkflow,
|
||||||
['6014aa0e6d8e98cdba90f5288295a7105d7d14ab0ca906f51465eeb478d5fea0']
|
/(?:vars|secrets)\.IPTVNATOR_WINDOWS_EMBEDDED_MPV_RUNTIME/
|
||||||
|
);
|
||||||
|
assert.doesNotMatch(buildAndMakeWorkflow, /zhongfly\/mpv-winbuild/);
|
||||||
|
assert.equal(windowsRuntimePin.repository, 'zhongfly/mpv-winbuild');
|
||||||
|
assert.match(windowsRuntimePin.asset.name, /^mpv-dev-lgpl-x86_64-/);
|
||||||
|
assert.match(windowsRuntimePin.asset.sha256, /^[a-f0-9]{64}$/);
|
||||||
|
assert.match(
|
||||||
|
windowsRuntimePin.upstream.licenseClaim,
|
||||||
|
/checksum and archive layout, not the complete transitive license closure/
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/node tools\/embedded-mpv\/update-windows-runtime-pin\.mjs/
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/node --test\s+tools\/embedded-mpv\/windows-runtime-pin\.test\.mjs/
|
||||||
|
);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/electron-package-identity\.test\.mjs/
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/automation\/windows-embedded-mpv-runtime-pin/
|
||||||
|
);
|
||||||
|
assert.match(windowsRuntimeRefreshWorkflow, /secrets\.PAT/);
|
||||||
|
assert.match(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/actions\/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1/
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/actions\/setup-node@820762786026740c76f36085b0efc47a31fe5020/
|
||||||
|
);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
windowsRuntimeRefreshWorkflow,
|
||||||
|
/actions\/(?:checkout|setup-node)@v\d+/
|
||||||
);
|
);
|
||||||
assert.match(buildAndMakeWorkflow, /refs\/tags\/v\*/);
|
|
||||||
assert.match(
|
assert.match(
|
||||||
buildAndMakeWorkflow,
|
buildAndMakeWorkflow,
|
||||||
/name:\s+Override Windows arch in electron-builder\.json/
|
/name:\s+Override Windows arch in electron-builder\.json/
|
||||||
@@ -927,6 +964,14 @@ test('Windows CI packages embedded MPV from a staged x64 runtime', () => {
|
|||||||
);
|
);
|
||||||
assert.match(embeddedMpvStageRuntimeSource, /\.dll\.a/);
|
assert.match(embeddedMpvStageRuntimeSource, /\.dll\.a/);
|
||||||
assert.match(embeddedMpvBuildSource, /\.dll\.a/);
|
assert.match(embeddedMpvBuildSource, /\.dll\.a/);
|
||||||
|
assert.match(
|
||||||
|
embeddedMpvWindowsArchiveStageSource,
|
||||||
|
/checksum-and-layout-only/
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
embeddedMpvWindowsArchiveStageSource,
|
||||||
|
/not-independently-verified/
|
||||||
|
);
|
||||||
assert.ok(requireEmbeddedMpvLines.length > 0);
|
assert.ok(requireEmbeddedMpvLines.length > 0);
|
||||||
for (const line of requireEmbeddedMpvLines) {
|
for (const line of requireEmbeddedMpvLines) {
|
||||||
assert.doesNotMatch(line, /cache-hit/);
|
assert.doesNotMatch(line, /cache-hit/);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"{workspaceRoot}/electron-builder.json",
|
"{workspaceRoot}/electron-builder.json",
|
||||||
"{workspaceRoot}/.github/workflows/build-and-make.yaml",
|
"{workspaceRoot}/.github/workflows/build-and-make.yaml",
|
||||||
"{workspaceRoot}/.github/workflows/publish-snap.yaml",
|
"{workspaceRoot}/.github/workflows/publish-snap.yaml",
|
||||||
|
"{workspaceRoot}/.github/workflows/refresh-windows-embedded-mpv-runtime.yaml",
|
||||||
"{workspaceRoot}/apps/electron-backend/build-embedded-mpv.js",
|
"{workspaceRoot}/apps/electron-backend/build-embedded-mpv.js",
|
||||||
"{workspaceRoot}/apps/electron-backend/project.json",
|
"{workspaceRoot}/apps/electron-backend/project.json",
|
||||||
"{workspaceRoot}/apps/electron-backend/native/src/embedded_mpv_win32.cc",
|
"{workspaceRoot}/apps/electron-backend/native/src/embedded_mpv_win32.cc",
|
||||||
@@ -52,10 +53,14 @@
|
|||||||
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.cjs",
|
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.cjs",
|
||||||
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.d.cts",
|
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.d.cts",
|
||||||
"{workspaceRoot}/tools/embedded-mpv/stage-runtime.mjs",
|
"{workspaceRoot}/tools/embedded-mpv/stage-runtime.mjs",
|
||||||
"{workspaceRoot}/tools/embedded-mpv/stage-windows-runtime-archive.mjs"
|
"{workspaceRoot}/tools/embedded-mpv/stage-windows-runtime-archive.mjs",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/update-windows-runtime-pin.mjs",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/windows-runtime-pin.json",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/windows-runtime-pin.mjs",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/windows-runtime-pin.test.mjs"
|
||||||
],
|
],
|
||||||
"options": {
|
"options": {
|
||||||
"command": "node --test tools/packaging/electron-package-identity.test.mjs tools/packaging/asar-dependency-closure.test.mjs tools/packaging/embedded-mpv-arch.test.mjs tools/packaging/flatpak-launcher-validation.test.mjs tools/packaging/configure-linux-frame-copy-build.test.mjs tools/packaging/linux-after-pack.test.mjs tools/packaging/linux-frame-copy-profile.test.mjs tools/packaging/prepare-linux-runtime-source-snapshot.test.mjs tools/packaging/publish-snap-workflow.test.mjs tools/packaging/release-snap-assets.test.mjs tools/packaging/verify-linux-frame-copy-runtime.test.mjs tools/embedded-mpv/build-linux-runtime.test.mjs tools/embedded-mpv/download-pinned-source.test.mjs tools/embedded-mpv/generate-linux-runtime-notices.test.mjs tools/embedded-mpv/linux-runtime-manifest.test.mjs",
|
"command": "node --test tools/packaging/electron-package-identity.test.mjs tools/packaging/asar-dependency-closure.test.mjs tools/packaging/embedded-mpv-arch.test.mjs tools/packaging/flatpak-launcher-validation.test.mjs tools/packaging/configure-linux-frame-copy-build.test.mjs tools/packaging/linux-after-pack.test.mjs tools/packaging/linux-frame-copy-profile.test.mjs tools/packaging/prepare-linux-runtime-source-snapshot.test.mjs tools/packaging/publish-snap-workflow.test.mjs tools/packaging/release-snap-assets.test.mjs tools/packaging/verify-linux-frame-copy-runtime.test.mjs tools/embedded-mpv/build-linux-runtime.test.mjs tools/embedded-mpv/download-pinned-source.test.mjs tools/embedded-mpv/generate-linux-runtime-notices.test.mjs tools/embedded-mpv/linux-runtime-manifest.test.mjs tools/embedded-mpv/windows-runtime-pin.test.mjs",
|
||||||
"cwd": "{workspaceRoot}"
|
"cwd": "{workspaceRoot}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -67,6 +72,7 @@
|
|||||||
"{workspaceRoot}/tools/eslint/**/*",
|
"{workspaceRoot}/tools/eslint/**/*",
|
||||||
"{workspaceRoot}/.github/workflows/build-and-make.yaml",
|
"{workspaceRoot}/.github/workflows/build-and-make.yaml",
|
||||||
"{workspaceRoot}/.github/workflows/publish-snap.yaml",
|
"{workspaceRoot}/.github/workflows/publish-snap.yaml",
|
||||||
|
"{workspaceRoot}/.github/workflows/refresh-windows-embedded-mpv-runtime.yaml",
|
||||||
"{workspaceRoot}/tools/packaging/prepare-linux-runtime-source-snapshot.cjs",
|
"{workspaceRoot}/tools/packaging/prepare-linux-runtime-source-snapshot.cjs",
|
||||||
"{workspaceRoot}/tools/packaging/prepare-linux-runtime-source-snapshot.test.mjs",
|
"{workspaceRoot}/tools/packaging/prepare-linux-runtime-source-snapshot.test.mjs",
|
||||||
"{workspaceRoot}/tools/packaging/publish-snap-workflow.test.mjs",
|
"{workspaceRoot}/tools/packaging/publish-snap-workflow.test.mjs",
|
||||||
@@ -81,9 +87,13 @@
|
|||||||
"{workspaceRoot}/tools/embedded-mpv/linux-source-archive-contract.cjs",
|
"{workspaceRoot}/tools/embedded-mpv/linux-source-archive-contract.cjs",
|
||||||
"{workspaceRoot}/tools/embedded-mpv/linux-source-archive-contract.d.cts",
|
"{workspaceRoot}/tools/embedded-mpv/linux-source-archive-contract.d.cts",
|
||||||
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.cjs",
|
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.cjs",
|
||||||
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.d.cts"
|
"{workspaceRoot}/tools/embedded-mpv/runtime-probe-contract.d.cts",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/stage-windows-runtime-archive.mjs",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/update-windows-runtime-pin.mjs",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/windows-runtime-pin.mjs",
|
||||||
|
"{workspaceRoot}/tools/embedded-mpv/windows-runtime-pin.test.mjs"
|
||||||
],
|
],
|
||||||
"command": "eslint \"tools/packaging/**/*.{js,cjs,mjs,ts}\" \"tools/embedded-mpv/build-macos-runtime.mjs\" \"tools/embedded-mpv/build-linux-runtime.{mjs,test.mjs}\" \"tools/embedded-mpv/download-pinned-source.{mjs,test.mjs}\" \"tools/embedded-mpv/generate-linux-runtime-notices.{cjs,test.mjs}\" \"tools/embedded-mpv/linux-source-archive-contract.cjs\" \"tools/embedded-mpv/runtime-probe-contract.cjs\""
|
"command": "eslint \"tools/packaging/**/*.{js,cjs,mjs,ts}\" \"tools/embedded-mpv/build-macos-runtime.mjs\" \"tools/embedded-mpv/build-linux-runtime.{mjs,test.mjs}\" \"tools/embedded-mpv/download-pinned-source.{mjs,test.mjs}\" \"tools/embedded-mpv/generate-linux-runtime-notices.{cjs,test.mjs}\" \"tools/embedded-mpv/linux-source-archive-contract.cjs\" \"tools/embedded-mpv/runtime-probe-contract.cjs\" \"tools/embedded-mpv/stage-windows-runtime-archive.mjs\" \"tools/embedded-mpv/update-windows-runtime-pin.mjs\" \"tools/embedded-mpv/windows-runtime-pin.{mjs,test.mjs}\""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tags": ["scope:tools", "domain:packaging", "type:tool"]
|
"tags": ["scope:tools", "domain:packaging", "type:tool"]
|
||||||
|
|||||||
Reference in new issue
Block a user