mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
* docs: design Linux frame-copy packaging * docs: plan Linux frame-copy packaging * feat(packaging): define Linux frame-copy profiles * fix(packaging): reject inherited profile names * feat(embedded-mpv): validate staged Linux runtime * fix(embedded-mpv): require Linux source packages * fix(embedded-mpv): harden Linux runtime staging * feat(embedded-mpv): build LGPL Linux runtime * fix(embedded-mpv): pin Linux runtime inputs * feat(embedded-mpv): build relocatable Linux helper * fix(embedded-mpv): require bundled Linux runtime * fix(embedded-mpv): make Linux runtime portable * feat(packaging): ship Linux frame-copy artifacts * fix(embedded-mpv): verify Linux helper linkage * fix(packaging): enforce Linux frame-copy isolation * fix(embedded-mpv): pin Linux display data * docs(embedded-mpv): document Linux frame-copy packaging * feat(embedded-mpv): probe Linux frame-copy runtime * test(embedded-mpv): smoke packaged Linux frame-copy * docs(embedded-mpv): clarify Linux system runtime baseline * fix(embedded-mpv): harden Linux runtime capability gate * ci: verify Linux frame-copy packages * test(embedded-mpv): harden packaged Linux smoke * test(embedded-mpv): preserve packaged GL mode * test(packaging): harden Linux package probes * fix(embedded-mpv): enable private Snap shared memory * fix(embedded-mpv): sanitize Linux helper environment * fix(packaging): enforce private Snap memory semantics * fix(packaging): reject ambiguous Snap memory metadata * fix(embedded-mpv): prioritize trusted Snap GL * fix(packaging): reject advanced Snap YAML semantics * fix(packaging): reject arbitrary Snap YAML aliases * feat(packaging): ship Linux runtime license notices * docs(embedded-mpv): document Linux runtime distribution * fix(packaging): parse Snap trailing comments safely * fix(release): gate Snap publish on public source release * fix(packaging): strip VCS metadata from source bundle * docs(packaging): clarify Linux source release gate * test(embedded-mpv): smoke missing bundled libmpv * style(embedded-mpv): format final validation inputs * fix(e2e): satisfy fixture index signature typing * fix(ci): declare fontconfig gperf generator * fix(embedded-mpv): hash runtime cache identities * fix(packaging): harden Linux frame-copy delivery * fix(packaging): tighten runtime delivery gates * fix(ci): decouple Linux runtime matrix * fix(packaging): harden Linux frame-copy delivery * fix(packaging): validate Linux frame-copy runtimes * fix(packaging): scope Snap Electron library checks * feat(packaging): ship Linux frame-copy runtimes * fix(packaging): improve Linux runtime smoke diagnostics * fix(packaging): expose bounded helper probe details * test(packaging): trace Snap EGL probe failures * fix(packaging): prefer core22 ABI in Snap helper * fix(packaging): bound helper probe capture * fix(packaging): harden Linux frame-copy releases * fix(packaging): canonicalize libplacebo submodule identity * fix(packaging): make source archive inspection portable * fix(packaging): harden Snap release verification
294 lines
8.7 KiB
JavaScript
294 lines
8.7 KiB
JavaScript
const linuxAfterPack = require('./linux-after-pack.cjs');
|
|
const {
|
|
isForeignLinuxEmbeddedMpvArch,
|
|
resolveElectronBuilderArchName,
|
|
validatePackagedEmbeddedMpv,
|
|
} = require('./embedded-mpv-packaging.cjs');
|
|
const {
|
|
resolveLinuxFrameCopyProfile,
|
|
validateLinuxProfileTargets,
|
|
} = require('./linux-frame-copy-profile.cjs');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const {
|
|
preparePackagedFrameCopyArtifacts,
|
|
} = require('./embedded-mpv-frame-copy-files.cjs');
|
|
|
|
function log(message) {
|
|
console.log(` - ${message}`);
|
|
}
|
|
|
|
function isTruthy(value) {
|
|
return ['1', 'true', 'yes', 'on'].includes(
|
|
String(value ?? '')
|
|
.trim()
|
|
.toLowerCase()
|
|
);
|
|
}
|
|
|
|
function copyEmbeddedMpvNativeOutput(
|
|
resourceDir,
|
|
projectDir,
|
|
platform,
|
|
preparationOptions
|
|
) {
|
|
const sourceDir = path.join(
|
|
projectDir,
|
|
'dist',
|
|
'apps',
|
|
'electron-backend',
|
|
'native'
|
|
);
|
|
|
|
if (!fs.existsSync(sourceDir)) {
|
|
return;
|
|
}
|
|
|
|
const destinationDir = path.join(
|
|
resourceDir,
|
|
'app.asar.unpacked',
|
|
'electron-backend',
|
|
'native'
|
|
);
|
|
|
|
fs.rmSync(destinationDir, { recursive: true, force: true });
|
|
fs.cpSync(sourceDir, destinationDir, { recursive: true });
|
|
|
|
const resolvedPreparationOptions =
|
|
platform === 'linux' && preparationOptions
|
|
? {
|
|
...preparationOptions,
|
|
noticeSourceDir: path.join(
|
|
projectDir,
|
|
'vendor',
|
|
'embedded-mpv',
|
|
'linux-x64',
|
|
'notices'
|
|
),
|
|
}
|
|
: preparationOptions;
|
|
return preparePackagedFrameCopyArtifacts(
|
|
destinationDir,
|
|
platform,
|
|
resolvedPreparationOptions
|
|
);
|
|
}
|
|
|
|
function writeEmbeddedMpvUnavailableMarker(resourceDir, targetArch) {
|
|
const destinationDir = path.join(
|
|
resourceDir,
|
|
'app.asar.unpacked',
|
|
'electron-backend',
|
|
'native'
|
|
);
|
|
|
|
fs.rmSync(destinationDir, { recursive: true, force: true });
|
|
fs.mkdirSync(destinationDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(destinationDir, 'embedded-mpv-unavailable.txt'),
|
|
`Embedded MPV is not bundled for ${targetArch} Linux builds yet. The built-in player and external MPV/VLC remain available.\n`
|
|
);
|
|
}
|
|
|
|
function resolveLinuxFrameCopyPackagingContext(
|
|
params,
|
|
{
|
|
required = isTruthy(process.env.IPTVNATOR_REQUIRE_EMBEDDED_MPV),
|
|
environment = process.env,
|
|
} = {}
|
|
) {
|
|
if (params.electronPlatformName !== 'linux') {
|
|
return null;
|
|
}
|
|
|
|
const targetArch = resolveElectronBuilderArchName(params.arch);
|
|
if (!targetArch) {
|
|
throw new Error(
|
|
`Unknown Electron Builder architecture: ${String(params.arch)}.`
|
|
);
|
|
}
|
|
const targetNames = [];
|
|
for (const target of params.targets ?? []) {
|
|
const targetName = String(target?.name ?? '')
|
|
.trim()
|
|
.toLowerCase();
|
|
if (!targetName) {
|
|
throw new Error(
|
|
'Linux Electron Builder targets must expose a non-empty name.'
|
|
);
|
|
}
|
|
if (targetNames.includes(targetName)) {
|
|
throw new Error(
|
|
`Linux Electron Builder target "${targetName}" is duplicated.`
|
|
);
|
|
}
|
|
targetNames.push(targetName);
|
|
}
|
|
if (targetNames.length === 0) {
|
|
throw new Error(
|
|
'Linux Electron Builder must provide at least one packaging target.'
|
|
);
|
|
}
|
|
|
|
// Official Linux frame-copy artifacts are intentionally x64-only. Do not
|
|
// let a caller-provided build-arch environment value promote an ARM
|
|
// package to a supported layout: every non-x64 target must remain the
|
|
// marker-only native-view fallback.
|
|
const foreignArch = targetArch !== 'x64';
|
|
const profileValue =
|
|
environment.IPTVNATOR_LINUX_FRAME_COPY_PROFILE?.trim() ?? '';
|
|
if (!profileValue) {
|
|
if (required && targetArch === 'x64') {
|
|
resolveLinuxFrameCopyProfile(profileValue);
|
|
}
|
|
return {
|
|
targetArch,
|
|
foreignArch,
|
|
targetNames,
|
|
profile: null,
|
|
};
|
|
}
|
|
|
|
const profile = resolveLinuxFrameCopyProfile(profileValue);
|
|
const targetErrors = validateLinuxProfileTargets(profile.name, targetNames);
|
|
if (targetErrors.length > 0) {
|
|
throw new Error(targetErrors.join('\n'));
|
|
}
|
|
return {
|
|
targetArch,
|
|
foreignArch,
|
|
targetNames,
|
|
profile,
|
|
};
|
|
}
|
|
|
|
function ensureSnapGraphicsContentMount(appOutDir, targetNames) {
|
|
if (!targetNames.includes('snap')) {
|
|
return null;
|
|
}
|
|
|
|
const mountPath = path.join(appOutDir, 'graphics');
|
|
if (!fs.existsSync(mountPath)) {
|
|
fs.mkdirSync(mountPath, { mode: 0o755 });
|
|
}
|
|
const stat = fs.lstatSync(mountPath);
|
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
throw new Error(
|
|
`Snap graphics content mount must be a real empty directory: ${mountPath}`
|
|
);
|
|
}
|
|
if (fs.readdirSync(mountPath).length > 0) {
|
|
throw new Error(
|
|
`Snap graphics content mount directory must be empty: ${mountPath}`
|
|
);
|
|
}
|
|
fs.chmodSync(mountPath, 0o755);
|
|
return mountPath;
|
|
}
|
|
|
|
async function afterPackHook(params) {
|
|
const requireEmbeddedMpv = isTruthy(
|
|
process.env.IPTVNATOR_REQUIRE_EMBEDDED_MPV
|
|
);
|
|
const linuxPackagingContext = resolveLinuxFrameCopyPackagingContext(
|
|
params,
|
|
{
|
|
required: requireEmbeddedMpv,
|
|
environment: process.env,
|
|
}
|
|
);
|
|
|
|
await linuxAfterPack(params);
|
|
if (linuxPackagingContext) {
|
|
const graphicsMountPath = ensureSnapGraphicsContentMount(
|
|
params.appOutDir,
|
|
linuxPackagingContext.targetNames
|
|
);
|
|
if (graphicsMountPath) {
|
|
log(
|
|
`prepared empty Snap graphics content mount at ${graphicsMountPath}`
|
|
);
|
|
}
|
|
}
|
|
|
|
log(
|
|
requireEmbeddedMpv
|
|
? `validating required embedded MPV ${params.electronPlatformName} runtime`
|
|
: `validating optional embedded MPV ${params.electronPlatformName} runtime`
|
|
);
|
|
const resourceDir = getResourceDir(params);
|
|
|
|
const foreignArch =
|
|
linuxPackagingContext?.foreignArch ??
|
|
isForeignLinuxEmbeddedMpvArch(params.electronPlatformName, params.arch);
|
|
if (foreignArch) {
|
|
const targetArch = linuxPackagingContext.targetArch;
|
|
log(
|
|
`embedded MPV addon is not built for ${targetArch}; packaging an unavailable marker instead`
|
|
);
|
|
writeEmbeddedMpvUnavailableMarker(resourceDir, targetArch);
|
|
} else {
|
|
copyEmbeddedMpvNativeOutput(
|
|
resourceDir,
|
|
params.packager.projectDir ?? process.cwd(),
|
|
params.electronPlatformName,
|
|
linuxPackagingContext
|
|
? {
|
|
profile: linuxPackagingContext.profile?.name,
|
|
targetNames: linuxPackagingContext.targetNames,
|
|
}
|
|
: undefined
|
|
);
|
|
}
|
|
|
|
const errors = validatePackagedEmbeddedMpv(resourceDir, {
|
|
platform: params.electronPlatformName,
|
|
required: requireEmbeddedMpv,
|
|
foreignArch,
|
|
targetArch: linuxPackagingContext?.targetArch,
|
|
profile: linuxPackagingContext?.profile?.name,
|
|
targetNames: linuxPackagingContext?.targetNames,
|
|
executableName: params.packager.executableName,
|
|
});
|
|
|
|
if (errors.length > 0) {
|
|
throw new Error(
|
|
[
|
|
`Embedded MPV ${params.electronPlatformName} package validation failed.`,
|
|
...errors.map((error) => `- ${error}`),
|
|
].join('\n')
|
|
);
|
|
}
|
|
|
|
log(`embedded MPV ${params.electronPlatformName} runtime validated`);
|
|
}
|
|
|
|
function getResourceDir(params) {
|
|
if (params.electronPlatformName !== 'darwin') {
|
|
return path.join(params.appOutDir, 'resources');
|
|
}
|
|
|
|
const appPath = params.appOutDir.endsWith('.app')
|
|
? params.appOutDir
|
|
: fs
|
|
.readdirSync(params.appOutDir)
|
|
.find((entry) => entry.endsWith('.app'));
|
|
|
|
return appPath
|
|
? path.join(
|
|
params.appOutDir.endsWith('.app')
|
|
? params.appOutDir
|
|
: path.join(params.appOutDir, appPath),
|
|
'Contents',
|
|
'Resources'
|
|
)
|
|
: params.appOutDir;
|
|
}
|
|
|
|
module.exports = afterPackHook;
|
|
module.exports.ensureSnapGraphicsContentMount = ensureSnapGraphicsContentMount;
|
|
module.exports.resolveLinuxFrameCopyPackagingContext =
|
|
resolveLinuxFrameCopyPackagingContext;
|
|
module.exports.writeEmbeddedMpvUnavailableMarker =
|
|
writeEmbeddedMpvUnavailableMarker;
|