mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 01:16: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
264 lines
8.5 KiB
JavaScript
264 lines
8.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { createRequire } from 'node:module';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const {
|
|
LINUX_SYSTEM_PACKAGE_DEPENDENCIES,
|
|
resolveLinuxFrameCopyProfile,
|
|
} = require('./linux-frame-copy-profile.cjs');
|
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
|
|
function cloneJson(value) {
|
|
return JSON.parse(JSON.stringify(value));
|
|
}
|
|
|
|
function targetName(target) {
|
|
const value =
|
|
typeof target === 'string'
|
|
? target
|
|
: target && typeof target === 'object'
|
|
? target.target
|
|
: null;
|
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
throw new Error(
|
|
'Electron Builder Linux targets must have a non-empty target name.'
|
|
);
|
|
}
|
|
return value.trim().toLowerCase();
|
|
}
|
|
|
|
function targetObject(target) {
|
|
return typeof target === 'string' ? { target } : { ...target };
|
|
}
|
|
|
|
function fpmDependencyName(option) {
|
|
return String(option).match(
|
|
/^--depends(?:=|\s+)([A-Za-z0-9+_.-]+)(?:$|\s|[<>=])/
|
|
)?.[1];
|
|
}
|
|
|
|
function configureSystemDependencies(config) {
|
|
for (const [format, dependencies] of Object.entries(
|
|
LINUX_SYSTEM_PACKAGE_DEPENDENCIES
|
|
)) {
|
|
const frameCopyDependencies = new Set(dependencies);
|
|
const formatConfig = { ...(config[format] ?? {}) };
|
|
const otherFpmOptions = (formatConfig.fpm ?? []).filter(
|
|
(option) => !frameCopyDependencies.has(fpmDependencyName(option))
|
|
);
|
|
formatConfig.fpm = [
|
|
...otherFpmOptions,
|
|
...dependencies.map((dependency) => `--depends=${dependency}`),
|
|
];
|
|
config[format] = formatConfig;
|
|
}
|
|
}
|
|
|
|
function removeForeignFrameCopyDependency(config, format) {
|
|
const dependencies = new Set(
|
|
LINUX_SYSTEM_PACKAGE_DEPENDENCIES[format] ?? []
|
|
);
|
|
const formatConfig = { ...(config[format] ?? {}) };
|
|
const retainedFpmOptions = (formatConfig.fpm ?? []).filter(
|
|
(option) => !dependencies.has(fpmDependencyName(option))
|
|
);
|
|
if (retainedFpmOptions.length > 0) {
|
|
formatConfig.fpm = retainedFpmOptions;
|
|
} else {
|
|
delete formatConfig.fpm;
|
|
}
|
|
config[format] = formatConfig;
|
|
}
|
|
|
|
export function configureLinuxFrameCopyBuild(
|
|
electronBuilderConfig,
|
|
{ profileName, foreignDeb = false, foreignArch } = {}
|
|
) {
|
|
const hasForeignArch = foreignArch !== undefined;
|
|
if (
|
|
!electronBuilderConfig ||
|
|
typeof electronBuilderConfig !== 'object' ||
|
|
Array.isArray(electronBuilderConfig)
|
|
) {
|
|
throw new TypeError(
|
|
'Electron Builder configuration must be an object.'
|
|
);
|
|
}
|
|
if (foreignDeb && profileName) {
|
|
throw new Error(
|
|
'The marker-only foreign DEB pass must not select a frame-copy profile.'
|
|
);
|
|
}
|
|
if (hasForeignArch && !foreignDeb) {
|
|
throw new Error(
|
|
'A marker-only foreign architecture requires --foreign-deb.'
|
|
);
|
|
}
|
|
const configured = cloneJson(electronBuilderConfig);
|
|
const targets = configured.linux?.target;
|
|
if (!Array.isArray(targets)) {
|
|
throw new Error('Electron Builder linux.target must be an array.');
|
|
}
|
|
|
|
if (foreignDeb) {
|
|
const debTarget = targets.find(
|
|
(target) => targetName(target) === 'deb'
|
|
);
|
|
if (!debTarget) {
|
|
throw new Error(
|
|
'Electron Builder has no DEB target for the foreign-architecture pass.'
|
|
);
|
|
}
|
|
const configuredDebTarget = targetObject(debTarget);
|
|
const configuredArches = Array.isArray(configuredDebTarget.arch)
|
|
? configuredDebTarget.arch
|
|
: [configuredDebTarget.arch].filter(Boolean);
|
|
const foreignArches = configuredArches.filter(
|
|
(architecture) => architecture !== 'x64'
|
|
);
|
|
if (foreignArches.length === 0) {
|
|
throw new Error(
|
|
'Electron Builder DEB target has no foreign architectures.'
|
|
);
|
|
}
|
|
if (
|
|
hasForeignArch &&
|
|
(typeof foreignArch !== 'string' ||
|
|
!foreignArches.includes(foreignArch))
|
|
) {
|
|
throw new Error(
|
|
`Unsupported marker-only foreign DEB architecture "${foreignArch}". Expected one of: ${foreignArches.join(
|
|
', '
|
|
)}.`
|
|
);
|
|
}
|
|
configuredDebTarget.arch = hasForeignArch
|
|
? [foreignArch]
|
|
: foreignArches;
|
|
configured.linux.target = [configuredDebTarget];
|
|
configured.directories = {
|
|
...(configured.directories ?? {}),
|
|
output: 'dist/executables-linux-foreign',
|
|
};
|
|
removeForeignFrameCopyDependency(configured, 'deb');
|
|
return configured;
|
|
}
|
|
|
|
const profile = resolveLinuxFrameCopyProfile(profileName);
|
|
const allowedTargets = new Set(profile.targets);
|
|
const targetsByName = new Map(
|
|
profile.targets.map((profileTarget) => [profileTarget, []])
|
|
);
|
|
for (const target of targets) {
|
|
const name = targetName(target);
|
|
if (allowedTargets.has(name)) {
|
|
targetsByName.get(name).push(target);
|
|
}
|
|
}
|
|
const duplicateTargets = profile.targets.filter(
|
|
(profileTarget) => targetsByName.get(profileTarget).length > 1
|
|
);
|
|
const missingTargets = profile.targets.filter(
|
|
(profileTarget) => targetsByName.get(profileTarget).length === 0
|
|
);
|
|
if (duplicateTargets.length > 0 || missingTargets.length > 0) {
|
|
throw new Error(
|
|
[
|
|
`Invalid Electron Builder targets for Linux profile "${profile.name}".`,
|
|
...(duplicateTargets.length > 0
|
|
? [
|
|
`Found duplicate target "${duplicateTargets.join(
|
|
'", "'
|
|
)}".`,
|
|
]
|
|
: []),
|
|
...(missingTargets.length > 0
|
|
? [`Missing targets: ${missingTargets.join(', ')}.`]
|
|
: []),
|
|
].join(' ')
|
|
);
|
|
}
|
|
configured.linux.target = profile.targets.map((profileTarget) => {
|
|
const target = targetsByName.get(profileTarget)[0];
|
|
const configuredTarget = targetObject(target);
|
|
if (profile.name === 'system') {
|
|
configuredTarget.arch = ['x64'];
|
|
}
|
|
return configuredTarget;
|
|
});
|
|
if (profile.name === 'system') {
|
|
configureSystemDependencies(configured);
|
|
}
|
|
return configured;
|
|
}
|
|
|
|
function parseArguments(argv) {
|
|
const normalized = argv[0] === '--' ? argv.slice(1) : argv;
|
|
let configPath = 'electron-builder.json';
|
|
let profileName;
|
|
let foreignDeb = false;
|
|
let foreignArch;
|
|
const nextValue = (flag, index) => {
|
|
const value = normalized[index + 1];
|
|
if (
|
|
typeof value !== 'string' ||
|
|
value.trim() === '' ||
|
|
value.startsWith('--')
|
|
) {
|
|
throw new Error(`${flag} requires a value.`);
|
|
}
|
|
return value;
|
|
};
|
|
for (let index = 0; index < normalized.length; index += 1) {
|
|
const argument = normalized[index];
|
|
if (argument === '--config') {
|
|
configPath = nextValue(argument, index);
|
|
index += 1;
|
|
} else if (argument === '--profile') {
|
|
profileName = nextValue(argument, index);
|
|
index += 1;
|
|
} else if (argument === '--foreign-deb') {
|
|
foreignDeb = true;
|
|
} else if (argument === '--foreign-arch') {
|
|
foreignArch = nextValue(argument, index);
|
|
index += 1;
|
|
} else {
|
|
throw new Error(`Unsupported configurator argument: ${argument}`);
|
|
}
|
|
}
|
|
if (!foreignDeb && !profileName) {
|
|
throw new Error('--profile or --foreign-deb is required.');
|
|
}
|
|
return {
|
|
configPath: path.resolve(configPath),
|
|
profileName,
|
|
foreignDeb,
|
|
foreignArch,
|
|
};
|
|
}
|
|
|
|
function main() {
|
|
const options = parseArguments(process.argv.slice(2));
|
|
const config = JSON.parse(fs.readFileSync(options.configPath, 'utf8'));
|
|
const configured = configureLinuxFrameCopyBuild(config, options);
|
|
fs.writeFileSync(
|
|
options.configPath,
|
|
`${JSON.stringify(configured, null, 4)}\n`
|
|
);
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === scriptPath) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
process.stderr.write(
|
|
`${error instanceof Error ? error.message : String(error)}\n`
|
|
);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|