ci(embedded-mpv): keep Windows runtime pin available (#1495)

This commit is contained in:
4gray authored and GitHub committed 2026-08-29 21:24:06 +02:00
1 parent 29ca94aa43
commit f04f67728e
14 files changed
+972 -75

No files matched your search

+16 -24
View File
@@ -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
+16
View File
@@ -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,
+16
View File
@@ -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`
+39 -12
View File
@@ -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
+2
View File
@@ -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",
+33 -8
View File
@@ -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."
}
}
+171
View File
@@ -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/);
+14 -4
View File
@@ -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"]