Files
iptvnator/tools/packaging/verify-linux-frame-copy-runtime.mjs
4gray 728df1a68c fix(packaging): restore Snap desktop runtime (#1406)
* fix(packaging): restore Snap desktop runtime

* docs(packaging): publish Snap launch repair note

* fix(packaging): declare Node 22.12 floor

* docs(architecture): update SQLite pin rationale

* fix(tooling): align Node engine floor

* fix(tooling): constrain supported Node releases

* docs(architecture): correct node-abi consumer
2026-08-11 02:08:30 +02:00

1828 lines
58 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import {
collectEmbeddedMpvNativeArchiveEntries,
listAsarPackageEntries,
} from './asar-dependency-closure.mjs';
const require = createRequire(import.meta.url);
const {
parseReadelfDynamic,
} = require('../embedded-mpv/build-linux-runtime.cjs');
const {
listElectronShippedLinuxLibraries,
validatePackagedEmbeddedMpv,
} = require('./embedded-mpv-packaging.cjs');
const {
LINUX_SYSTEM_PACKAGE_DEPENDENCIES,
resolveLinuxFrameCopyProfile,
} = require('./linux-frame-copy-profile.cjs');
const { resolveLinuxLauncherLayout } = require('./linux-launcher-layout.cjs');
const {
PACKAGE_VERIFICATION_PROBE_MAX_ATTEMPTS,
PACKAGE_VERIFICATION_PROBE_TIMEOUT_MS,
RUNTIME_PROBE_MAX_BUFFER_BYTES,
} = require('../embedded-mpv/runtime-probe-contract.cjs');
const scriptPath = fileURLToPath(import.meta.url);
const LIBMPV_DEPENDENCY_PATTERN = /^libmpv\.so(?:\.|$)/;
const SNAP_METADATA_MAX_BYTES = 256 * 1024;
const SNAP_DESKTOP_RUNTIME_SCRIPTS = [
'desktop-init.sh',
'desktop-common.sh',
'desktop-gnome-specific.sh',
];
// Keep this set identical to the packaged helper sanitizer in
// embedded-mpv-frame-copy-runtime/helper-environment.ts. Feature/debug
// selectors such as LIBGL_ALWAYS_SOFTWARE remain intentionally available.
const UNSAFE_RUNTIME_PROBE_ENVIRONMENT_VARIABLES = [
'BASH_ENV',
'ENV',
'BASHOPTS',
'SHELLOPTS',
'PS4',
'BASH_XTRACEFD',
'CDPATH',
'LD_AUDIT',
'LD_LIBRARY_PATH',
'LD_ORIGIN_PATH',
'LD_PRELOAD',
'__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS',
'__EGL_EXTERNAL_PLATFORM_CONFIG_FILENAMES',
'__EGL_VENDOR_LIBRARY_DIRS',
'__EGL_VENDOR_LIBRARY_FILENAMES',
'GBM_BACKEND',
'GBM_BACKENDS_PATH',
'LIBGL_DRIVERS_PATH',
'MESA_LOADER_DRIVER_OVERRIDE',
'LIBVA_DRIVER_NAME',
'LIBVA_DRIVERS_PATH',
'VDPAU_DRIVER_PATH',
'VK_DRIVER_FILES',
'VK_ICD_FILENAMES',
'VK_ADD_DRIVER_FILES',
'VK_ADD_LAYER_PATH',
'VK_IMPLICIT_LAYER_PATH',
'VK_ADD_IMPLICIT_LAYER_PATH',
'VK_LAYER_PATH',
];
export function runVerifierCommand(command, args, options = {}) {
const spawnOptions = {
cwd: options.cwd,
env: options.env,
encoding: 'utf8',
stdio: 'pipe',
timeout: options.timeout,
killSignal: options.killSignal,
windowsHide: true,
};
if (options.maxBuffer !== undefined) {
spawnOptions.maxBuffer = options.maxBuffer;
}
return spawnSync(command, args, spawnOptions);
}
function assertCommandSucceeded(command, args, result) {
if (result?.error) {
throw new Error(
`Unable to run ${command}: ${
result.error instanceof Error
? result.error.message
: String(result.error)
}`
);
}
if (result?.status !== 0) {
const details = [result?.stdout, result?.stderr]
.filter(Boolean)
.join('\n')
.trim();
throw new Error(
`${command} ${args.join(' ')} failed with status ${
result?.status ?? 'unknown'
}.${details ? `\n${details}` : ''}`
);
}
return result;
}
export function detectLinuxArtifactFormat(artifactPath) {
const fileName = path.basename(artifactPath);
if (/\.AppImage$/i.test(fileName)) {
return 'appimage';
}
if (/\.deb$/i.test(fileName)) {
return 'deb';
}
if (/\.rpm$/i.test(fileName)) {
return 'rpm';
}
if (/\.(?:pacman|pkg\.tar(?:\.[A-Za-z0-9]+)*)$/i.test(fileName)) {
return 'pacman';
}
if (/\.snap$/i.test(fileName)) {
return 'snap';
}
if (/\.flatpak$/i.test(fileName)) {
return 'flatpak';
}
throw new Error(`Unsupported Linux package artifact: ${artifactPath}`);
}
export function parseVerifierArguments(argv) {
const normalizedArgs = argv[0] === '--' ? argv.slice(1) : [...argv];
let artifactValue;
let profileValue;
for (let index = 0; index < normalizedArgs.length; index += 1) {
const argument = normalizedArgs[index];
if (argument === '--artifact') {
if (artifactValue !== undefined) {
throw new Error('Received duplicate --artifact argument.');
}
artifactValue = normalizedArgs[++index];
continue;
}
if (argument === '--profile') {
if (profileValue !== undefined) {
throw new Error('Received duplicate --profile argument.');
}
profileValue = normalizedArgs[++index];
continue;
}
throw new Error(`Unsupported verifier argument: ${argument}`);
}
if (!artifactValue) {
throw new Error('--artifact is required.');
}
if (!profileValue) {
throw new Error('--profile is required.');
}
const artifactPath = path.resolve(artifactValue);
const stat = fs.lstatSync(artifactPath);
if (!stat.isFile() || stat.isSymbolicLink()) {
throw new Error(
`Linux package artifact must be a regular file: ${artifactPath}`
);
}
const profile = resolveLinuxFrameCopyProfile(profileValue);
detectLinuxArtifactFormat(artifactPath);
return {
artifactPath,
profileName: profile.name,
};
}
export function findAppImageSquashfsOffsets(artifactPath) {
const magic = Buffer.from('hsqs');
const chunkSize = 1024 * 1024;
const descriptor = fs.openSync(artifactPath, 'r');
const offsets = [];
let position = 0;
let overlap = Buffer.alloc(0);
try {
while (true) {
const chunk = Buffer.alloc(chunkSize);
const bytesRead = fs.readSync(
descriptor,
chunk,
0,
chunk.length,
position
);
if (bytesRead === 0) {
break;
}
const contents = Buffer.concat([
overlap,
chunk.subarray(0, bytesRead),
]);
const contentsStart = position - overlap.length;
let searchOffset = 0;
while (searchOffset <= contents.length - magic.length) {
const matchOffset = contents.indexOf(magic, searchOffset);
if (matchOffset === -1) {
break;
}
const absoluteOffset = contentsStart + matchOffset;
if (offsets.at(-1) !== absoluteOffset) {
offsets.push(absoluteOffset);
}
searchOffset = matchOffset + 1;
}
overlap = contents.subarray(
Math.max(0, contents.length - (magic.length - 1))
);
position += bytesRead;
}
} finally {
fs.closeSync(descriptor);
}
return offsets;
}
export function extractLinuxArtifact({
artifactPath,
format,
destination,
runCommand = runVerifierCommand,
}) {
fs.rmSync(destination, { recursive: true, force: true });
fs.mkdirSync(path.dirname(destination), { recursive: true });
const run = (command, args, options = {}) =>
assertCommandSucceeded(
command,
args,
runCommand(command, args, options)
);
switch (format) {
case 'appimage': {
const squashfsOffsets = findAppImageSquashfsOffsets(artifactPath);
if (squashfsOffsets.length === 0) {
throw new Error(
`AppImage contains no SquashFS payload: ${artifactPath}`
);
}
const failures = [];
for (const offset of squashfsOffsets) {
fs.rmSync(destination, { recursive: true, force: true });
const args = [
'-no-progress',
'-offset',
String(offset),
'-dest',
destination,
artifactPath,
];
const result = runCommand('unsquashfs', args);
if (!result?.error && result?.status === 0) {
return destination;
}
failures.push(
[result?.stdout, result?.stderr]
.filter(Boolean)
.join('\n')
.trim()
);
}
throw new Error(
[
`Unable to extract the AppImage SquashFS payload at any candidate offset: ${artifactPath}`,
...failures.filter(Boolean),
].join('\n')
);
}
case 'deb':
fs.mkdirSync(destination, { recursive: true });
run('dpkg-deb', ['--extract', artifactPath, destination]);
return destination;
case 'rpm':
case 'pacman':
fs.mkdirSync(destination, { recursive: true });
run('bsdtar', [
'--extract',
'--file',
artifactPath,
'--directory',
destination,
]);
return destination;
case 'snap':
run('unsquashfs', [
'-no-progress',
'-dest',
destination,
artifactPath,
]);
return destination;
case 'flatpak': {
fs.mkdirSync(destination, { recursive: true });
const repository = path.join(destination, '.ostree-repository');
const checkout = path.join(destination, 'checkout');
fs.mkdirSync(repository, { recursive: true });
run('ostree', [
`--repo=${repository}`,
'init',
'--mode=archive-z2',
]);
run('flatpak', ['build-import-bundle', repository, artifactPath]);
const refsResult = run('ostree', ['refs', `--repo=${repository}`]);
const refs = String(refsResult.stdout ?? '')
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line.startsWith('app/'));
if (refs.length !== 1) {
throw new Error(
`Flatpak bundle must import exactly one application ref; received ${
refs.length > 0 ? refs.join(', ') : '<none>'
}.`
);
}
run('ostree', [
'checkout',
'-U',
`--repo=${repository}`,
refs[0],
checkout,
]);
return checkout;
}
default:
throw new Error(`Unsupported Linux package format: ${format}`);
}
}
export function findExtractedResourceDir(extractionRoot) {
const candidates = [];
function visit(directoryPath) {
let entries;
try {
entries = fs.readdirSync(directoryPath, { withFileTypes: true });
} catch (error) {
throw new Error(
`Unable to inspect extracted package directory ${directoryPath}: ${
error instanceof Error ? error.message : String(error)
}`
);
}
for (const entry of entries) {
if (!entry.isDirectory() || entry.isSymbolicLink()) {
continue;
}
const entryPath = path.join(directoryPath, entry.name);
if (entry.name === 'resources') {
const nativeDir = path.join(
entryPath,
'app.asar.unpacked',
'electron-backend',
'native'
);
let nativeStat;
try {
nativeStat = fs.lstatSync(nativeDir);
} catch {
nativeStat = null;
}
if (nativeStat?.isDirectory() && !nativeStat.isSymbolicLink()) {
candidates.push(entryPath);
continue;
}
}
visit(entryPath);
}
}
visit(extractionRoot);
if (candidates.length !== 1) {
throw new Error(
`Expected exactly one embedded MPV native payload in ${extractionRoot}; found ${candidates.length}.`
);
}
return candidates[0];
}
function stripYamlTrailingComment(value) {
let quote = null;
for (let index = 0; index < value.length; index += 1) {
const character = value[index];
if (quote === "'") {
if (character === "'" && value[index + 1] === "'") {
index += 1;
} else if (character === "'") {
quote = null;
}
continue;
}
if (quote === '"') {
if (character === '\\') {
index += 1;
} else if (character === '"') {
quote = null;
}
continue;
}
if (character === "'" || character === '"') {
quote = character;
continue;
}
if (
character === '#' &&
(index === 0 || /[ \t]/.test(value[index - 1]))
) {
return value.slice(0, index).trimEnd();
}
}
return value;
}
function yamlMappingEntries(lines, key, indent, start = 0, end = lines.length) {
const prefix = `${' '.repeat(indent)}${key}:`;
return lines
.map((line, index) => ({ index, line }))
.filter(
({ index, line }) =>
index >= start &&
index < end &&
line.startsWith(prefix) &&
line.slice(prefix.length).match(/^(?:\s|$)/) &&
line.length - line.trimStart().length === indent
)
.map(({ index, line }) => {
let blockEnd = end;
for (
let candidateIndex = index + 1;
candidateIndex < end;
candidateIndex += 1
) {
const candidate = lines[candidateIndex];
if (
!candidate.trim() ||
candidate.trimStart().startsWith('#')
) {
continue;
}
const candidateIndent =
candidate.length - candidate.trimStart().length;
if (candidateIndent <= indent) {
blockEnd = candidateIndex;
break;
}
}
return {
index,
end: blockEnd,
value: stripYamlTrailingComment(
line.slice(prefix.length).trim()
),
};
});
}
function singleYamlMappingEntry(lines, key, indent, start, end) {
const entries = yamlMappingEntries(lines, key, indent, start, end);
return entries.length === 1 ? entries[0] : null;
}
function directYamlMappingEntries(lines, parent, indent) {
const entries = [];
for (let index = parent.index + 1; index < parent.end; index += 1) {
const line = lines[index];
if (!line.trim() || line.trimStart().startsWith('#')) {
continue;
}
const lineIndent = line.length - line.trimStart().length;
if (lineIndent !== indent) {
continue;
}
const match = line
.slice(indent)
.match(/^([A-Za-z0-9][A-Za-z0-9_-]*):(?:\s*(.*))?$/);
if (!match) {
return null;
}
let blockEnd = parent.end;
for (
let candidateIndex = index + 1;
candidateIndex < parent.end;
candidateIndex += 1
) {
const candidate = lines[candidateIndex];
if (!candidate.trim() || candidate.trimStart().startsWith('#')) {
continue;
}
const candidateIndent =
candidate.length - candidate.trimStart().length;
if (candidateIndent <= indent) {
blockEnd = candidateIndex;
break;
}
}
entries.push({
key: match[1],
value: stripYamlTrailingComment(match[2] ?? ''),
index,
end: blockEnd,
});
}
return entries;
}
function directYamlAbsolutePathMappingEntries(lines, parent, indent) {
const entries = [];
for (let index = parent.index + 1; index < parent.end; index += 1) {
const line = lines[index];
if (!line.trim() || line.trimStart().startsWith('#')) {
continue;
}
const lineIndent = line.length - line.trimStart().length;
if (lineIndent !== indent) {
continue;
}
const match = line
.slice(indent)
.match(/^(\/[A-Za-z0-9._/-]+):(?:\s*(.*))?$/);
if (!match) {
return null;
}
let blockEnd = parent.end;
for (
let candidateIndex = index + 1;
candidateIndex < parent.end;
candidateIndex += 1
) {
const candidate = lines[candidateIndex];
if (!candidate.trim() || candidate.trimStart().startsWith('#')) {
continue;
}
const candidateIndent =
candidate.length - candidate.trimStart().length;
if (candidateIndent <= indent) {
blockEnd = candidateIndex;
break;
}
}
entries.push({
key: match[1],
value: stripYamlTrailingComment(match[2] ?? ''),
index,
end: blockEnd,
});
}
return entries;
}
function yamlScalarEquals(value, expected) {
return (
value === expected ||
value === JSON.stringify(expected) ||
value === `'${expected.replaceAll("'", "''")}'`
);
}
function yamlSequenceIncludes(lines, entry, expected) {
if (entry.value) {
if (!entry.value.startsWith('[') || !entry.value.endsWith(']')) {
return false;
}
const values = entry.value
.slice(1, -1)
.split(',')
.map((value) => value.trim());
return (
values.length > 0 &&
values.every(
(value) =>
value.length > 0 &&
!/[:[\]{}&*]/.test(value) &&
!value.startsWith('-')
) &&
values.some((value) => yamlScalarEquals(value, expected))
);
}
const sequenceLines = lines
.slice(entry.index + 1, entry.end)
.filter((line) => line.trim() && !line.trimStart().startsWith('#'));
const itemIndent = 6;
const values = sequenceLines.map((line) => {
const lineIndent = line.length - line.trimStart().length;
if (lineIndent !== itemIndent) {
return null;
}
const match = line.trim().match(/^-\s+([^:[\]{}&*]+)$/);
return match ? stripYamlTrailingComment(match[1].trim()) : null;
});
return (
values.length > 0 &&
values.every((value) => value !== null) &&
values.some((value) => yamlScalarEquals(value, expected))
);
}
function yamlSyntaxOutsideQuotedScalars(line) {
let result = '';
let quote = null;
for (let index = 0; index < line.length; index += 1) {
const character = line[index];
if (quote === "'") {
if (character === "'" && line[index + 1] === "'") {
result += ' ';
index += 1;
} else {
result += ' ';
if (character === "'") {
quote = null;
}
}
continue;
}
if (quote === '"') {
result += ' ';
if (character === '\\') {
result += ' ';
index += 1;
} else if (character === '"') {
quote = null;
}
continue;
}
if (character === '#') {
break;
}
if (character === "'" || character === '"') {
quote = character;
result += ' ';
continue;
}
result += character;
}
return result;
}
function containsUnsupportedYamlSemantics(lines) {
let blockScalarParentIndent = null;
for (const line of lines) {
if (!line.trim()) {
continue;
}
const lineIndent = line.length - line.trimStart().length;
if (
blockScalarParentIndent !== null &&
lineIndent > blockScalarParentIndent
) {
continue;
}
blockScalarParentIndent = null;
const syntax = yamlSyntaxOutsideQuotedScalars(line);
if (
/(?:^|[\s:[\]{},])(?:[&*][^\s,[\]{}]+|![^\s,[\]{}]+)(?=$|[\s,\]}])/.test(
syntax
) ||
/(?:^|\s)<<\s*:/.test(syntax)
) {
return true;
}
if (/:\s*[>|][+-]?\d?\s*$/.test(syntax)) {
blockScalarParentIndent = lineIndent;
}
}
return false;
}
export function validateExtractedSnapMetadata(extractionRoot) {
const snapYamlPath = path.join(extractionRoot, 'meta', 'snap.yaml');
let stat;
try {
stat = fs.lstatSync(snapYamlPath);
} catch {
return [`Missing extracted Snap metadata: ${snapYamlPath}`];
}
if (!stat.isFile() || stat.isSymbolicLink()) {
return [
`Extracted Snap metadata must be a regular file: ${snapYamlPath}`,
];
}
if (stat.size > SNAP_METADATA_MAX_BYTES) {
return [
`Extracted Snap metadata exceeds the ${String(
SNAP_METADATA_MAX_BYTES
)}-byte size limit: ${snapYamlPath}`,
];
}
let contents;
try {
contents = fs.readFileSync(snapYamlPath, 'utf8');
} catch (error) {
return [
`Unable to read extracted Snap metadata at ${snapYamlPath}: ${
error instanceof Error ? error.message : String(error)
}`,
];
}
if (contents.includes('\t')) {
return [
`Extracted Snap metadata must use space indentation: ${snapYamlPath}`,
];
}
const lines = contents.split(/\r?\n/);
if (containsUnsupportedYamlSemantics(lines)) {
return [
'Extracted Snap metadata must not use YAML anchors, aliases, merge keys, or custom tags.',
];
}
const errors = [];
for (const script of SNAP_DESKTOP_RUNTIME_SCRIPTS) {
const desktopScriptPath = path.join(extractionRoot, script);
let desktopScriptStat;
try {
desktopScriptStat = fs.lstatSync(desktopScriptPath);
} catch {
errors.push(
`Missing required desktop runtime script: ${desktopScriptPath}`
);
continue;
}
if (
!desktopScriptStat.isFile() ||
desktopScriptStat.isSymbolicLink()
) {
errors.push(
`Extracted Snap desktop runtime script must be a regular file: ${desktopScriptPath}`
);
continue;
}
if (
script === 'desktop-init.sh' &&
(desktopScriptStat.mode & 0o111) === 0
) {
errors.push(
`Extracted Snap desktop-init.sh must be executable: ${desktopScriptPath}`
);
}
}
const graphicsMountPath = path.join(extractionRoot, 'graphics');
let graphicsMountStat;
try {
graphicsMountStat = fs.lstatSync(graphicsMountPath);
} catch {
errors.push(
`Extracted Snap must contain an empty graphics content mount directory: ${graphicsMountPath}`
);
}
if (
graphicsMountStat &&
(!graphicsMountStat.isDirectory() || graphicsMountStat.isSymbolicLink())
) {
errors.push(
`Extracted Snap graphics content mount must be a real directory: ${graphicsMountPath}`
);
} else if (graphicsMountStat) {
if ((graphicsMountStat.mode & 0o777) !== 0o755) {
errors.push(
`Extracted Snap graphics content mount directory must have mode 0755: ${graphicsMountPath}`
);
}
try {
if (fs.readdirSync(graphicsMountPath).length > 0) {
errors.push(
`Extracted Snap graphics content mount directory must be empty: ${graphicsMountPath}`
);
}
} catch (error) {
errors.push(
`Unable to inspect extracted Snap graphics content mount at ${graphicsMountPath}: ${
error instanceof Error ? error.message : String(error)
}`
);
}
}
const base = singleYamlMappingEntry(lines, 'base', 0, 0, lines.length);
if (!base || !yamlScalarEquals(base.value, 'core22')) {
errors.push('Extracted Snap metadata must declare base: core22.');
}
const confinement = singleYamlMappingEntry(
lines,
'confinement',
0,
0,
lines.length
);
if (!confinement || !yamlScalarEquals(confinement.value, 'strict')) {
errors.push(
'Extracted Snap metadata must declare confinement: strict.'
);
}
const layout = singleYamlMappingEntry(lines, 'layout', 0, 0, lines.length);
if (!layout || layout.value) {
errors.push(
'Extracted Snap metadata must declare the exact Snap graphics layout contract.'
);
} else {
const layoutEntries = directYamlAbsolutePathMappingEntries(
lines,
layout,
2
);
const expectedLayouts = [
{
path: '/usr/share/libdrm',
field: 'bind',
target: '$SNAP/graphics/libdrm',
label: 'libdrm',
},
{
path: '/usr/share/drirc.d',
field: 'symlink',
target: '$SNAP/graphics/drirc.d',
label: 'drirc.d',
},
];
if (
layoutEntries &&
new Set(layoutEntries.map(({ key }) => key)).size !==
layoutEntries.length
) {
errors.push('Extracted Snap layout paths must be unique.');
}
if (
!layoutEntries ||
layoutEntries.length !== expectedLayouts.length ||
expectedLayouts.some(
({ path: expectedPath }) =>
layoutEntries.filter(({ key }) => key === expectedPath)
.length !== 1
)
) {
errors.push(
'Extracted Snap graphics layout must contain exactly the libdrm and drirc.d entries.'
);
}
for (const expected of expectedLayouts) {
const entry = layoutEntries?.find(
({ key }) => key === expected.path
);
const fields =
entry && !entry.value
? directYamlMappingEntries(lines, entry, 4)
: null;
if (
!fields ||
fields.length !== 1 ||
fields[0].key !== expected.field
) {
errors.push(
`Extracted Snap ${expected.label} layout must contain exactly ${expected.field}.`
);
}
if (
!fields ||
fields.filter(
({ key, value }) =>
key === expected.field &&
yamlScalarEquals(value, expected.target)
).length !== 1
) {
errors.push(
`Extracted Snap ${expected.label} layout must declare ${expected.field}: ${expected.target}.`
);
}
}
}
const plugs = singleYamlMappingEntry(lines, 'plugs', 0, 0, lines.length);
const sharedMemory =
plugs &&
singleYamlMappingEntry(
lines,
'shared-memory',
2,
plugs.index + 1,
plugs.end
);
const graphicsCore22 =
plugs &&
singleYamlMappingEntry(
lines,
'graphics-core22',
2,
plugs.index + 1,
plugs.end
);
if (!plugs || plugs.value || !sharedMemory || sharedMemory.value) {
errors.push(
'Extracted Snap metadata must declare exactly one top-level shared-memory plug.'
);
} else {
const fields = directYamlMappingEntries(lines, sharedMemory, 4);
const interfaceEntries =
fields?.filter(({ key }) => key === 'interface') ?? [];
const privateEntries =
fields?.filter(({ key }) => key === 'private') ?? [];
const interfaceEntry = interfaceEntries[0];
const privateEntry = privateEntries[0];
if (
!fields ||
fields.length !== 2 ||
interfaceEntries.length !== 1 ||
privateEntries.length !== 1
) {
errors.push(
'Extracted Snap private shared-memory plug must contain exactly interface and private.'
);
}
if (
!interfaceEntry ||
!yamlScalarEquals(interfaceEntry.value, 'shared-memory')
) {
errors.push(
'Extracted Snap top-level shared-memory plug must declare interface: shared-memory.'
);
}
if (!privateEntry || privateEntry.value !== 'true') {
errors.push(
'Extracted Snap top-level shared-memory plug must declare private: true.'
);
}
const plugEntries = directYamlMappingEntries(lines, plugs, 2);
if (!plugEntries || plugEntries.some(({ value }) => value.length > 0)) {
errors.push(
'Extracted Snap plug declarations must use block mappings.'
);
}
if (
plugEntries &&
new Set(plugEntries.map(({ key }) => key)).size !==
plugEntries.length
) {
errors.push('Extracted Snap direct plug keys must be unique.');
}
const sharedMemoryInterfaces =
plugEntries?.filter((plugEntry) => {
const plugFields = directYamlMappingEntries(
lines,
plugEntry,
4
);
const interfaceFields =
plugFields?.filter(({ key }) => key === 'interface') ?? [];
return (
interfaceFields.length === 1 &&
yamlScalarEquals(interfaceFields[0].value, 'shared-memory')
);
}) ?? [];
if (
!plugEntries ||
sharedMemoryInterfaces.length !== 1 ||
sharedMemoryInterfaces[0].key !== 'shared-memory'
) {
errors.push(
'Extracted Snap metadata must declare exactly one plug with the shared-memory interface.'
);
}
}
if (!plugs || plugs.value || !graphicsCore22 || graphicsCore22.value) {
errors.push(
'Extracted Snap metadata must declare exactly one top-level graphics-core22 plug.'
);
} else {
const fields = directYamlMappingEntries(lines, graphicsCore22, 4);
const interfaceEntries =
fields?.filter(({ key }) => key === 'interface') ?? [];
const targetEntries =
fields?.filter(({ key }) => key === 'target') ?? [];
const providerEntries =
fields?.filter(({ key }) => key === 'default-provider') ?? [];
const interfaceEntry = interfaceEntries[0];
const targetEntry = targetEntries[0];
const providerEntry = providerEntries[0];
if (
!fields ||
fields.length !== 3 ||
interfaceEntries.length !== 1 ||
targetEntries.length !== 1 ||
providerEntries.length !== 1
) {
errors.push(
'Extracted Snap graphics-core22 plug must contain exactly interface, target, and default-provider.'
);
}
if (
!interfaceEntry ||
!yamlScalarEquals(interfaceEntry.value, 'content')
) {
errors.push(
'Extracted Snap graphics-core22 plug must declare interface: content.'
);
}
if (
!targetEntry ||
!yamlScalarEquals(targetEntry.value, '$SNAP/graphics')
) {
errors.push(
'Extracted Snap graphics-core22 plug must declare target: $SNAP/graphics.'
);
}
if (
!providerEntry ||
!yamlScalarEquals(providerEntry.value, 'mesa-core22')
) {
errors.push(
'Extracted Snap graphics-core22 plug must declare default-provider: mesa-core22.'
);
}
const plugEntries = directYamlMappingEntries(lines, plugs, 2);
const graphicsContracts =
plugEntries?.filter((plugEntry) => {
const plugFields = directYamlMappingEntries(
lines,
plugEntry,
4
);
if (!plugFields || plugFields.length !== 3) {
return false;
}
return [
['interface', 'content'],
['target', '$SNAP/graphics'],
['default-provider', 'mesa-core22'],
].every(
([key, expected]) =>
plugFields.filter(
({ key: fieldKey, value }) =>
fieldKey === key &&
yamlScalarEquals(value, expected)
).length === 1
);
}) ?? [];
if (
!plugEntries ||
graphicsContracts.length !== 1 ||
graphicsContracts[0].key !== 'graphics-core22'
) {
errors.push(
'Extracted Snap metadata must declare exactly one plug with the graphics-core22 contract.'
);
}
const graphicsTargets =
plugEntries?.filter((plugEntry) => {
const plugFields = directYamlMappingEntries(
lines,
plugEntry,
4
);
return (
plugFields?.some(
({ key, value }) =>
key === 'target' &&
yamlScalarEquals(value, '$SNAP/graphics')
) ?? false
);
}) ?? [];
if (
!plugEntries ||
graphicsTargets.length !== 1 ||
graphicsTargets[0].key !== 'graphics-core22'
) {
errors.push(
'Extracted Snap metadata must declare exactly one plug with target $SNAP/graphics.'
);
}
}
const slotsEntries = yamlMappingEntries(lines, 'slots', 0, 0, lines.length);
let hasSharedMemorySlot = slotsEntries.length > 1;
let invalidSlotsShape = slotsEntries.some(({ value }) => value.length > 0);
for (const slots of slotsEntries) {
const slotEntries = directYamlMappingEntries(lines, slots, 2);
if (!slotEntries) {
invalidSlotsShape = true;
continue;
}
invalidSlotsShape ||= slotEntries.some(({ value }) => value.length > 0);
hasSharedMemorySlot ||= slotEntries.some((slotEntry) => {
if (slotEntry.key === 'shared-memory') {
return true;
}
const slotFields = directYamlMappingEntries(lines, slotEntry, 4);
return (
slotFields?.some(
({ key, value }) =>
key === 'interface' &&
yamlScalarEquals(value, 'shared-memory')
) ?? false
);
});
}
if (invalidSlotsShape) {
errors.push(
'Extracted Snap slots must use block mappings with scalar fields.'
);
}
if (hasSharedMemorySlot) {
errors.push(
'Extracted Snap metadata must not declare a shared-memory slot.'
);
}
const apps = singleYamlMappingEntry(lines, 'apps', 0, 0, lines.length);
const app =
apps &&
singleYamlMappingEntry(lines, 'iptvnator', 2, apps.index + 1, apps.end);
const appPlugs =
app &&
singleYamlMappingEntry(lines, 'plugs', 4, app.index + 1, app.end);
const appEnvironment =
app &&
singleYamlMappingEntry(lines, 'environment', 4, app.index + 1, app.end);
const snapDesktopRuntime =
appEnvironment &&
singleYamlMappingEntry(
lines,
'SNAP_DESKTOP_RUNTIME',
6,
appEnvironment.index + 1,
appEnvironment.end
);
if (
!apps ||
apps.value ||
!app ||
app.value ||
!appPlugs ||
!yamlSequenceIncludes(lines, appPlugs, 'shared-memory')
) {
errors.push(
'Extracted Snap iptvnator app scalar sequence must contain the shared-memory plug.'
);
}
if (
!apps ||
apps.value ||
!app ||
app.value ||
!appPlugs ||
!yamlSequenceIncludes(lines, appPlugs, 'graphics-core22')
) {
errors.push(
'Extracted Snap iptvnator app scalar sequence must contain the graphics-core22 plug.'
);
}
if (
!appEnvironment ||
appEnvironment.value ||
!snapDesktopRuntime ||
!yamlScalarEquals(snapDesktopRuntime.value, '$SNAP/gnome-platform')
) {
errors.push(
'Extracted Snap iptvnator app environment must declare SNAP_DESKTOP_RUNTIME: $SNAP/gnome-platform.'
);
}
return errors;
}
export function readElfArchitecture(binaryPath) {
const descriptor = fs.openSync(binaryPath, 'r');
try {
const header = Buffer.alloc(20);
const bytesRead = fs.readSync(descriptor, header, 0, header.length, 0);
if (
bytesRead !== header.length ||
!header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46]))
) {
throw new Error(`Not an ELF binary: ${binaryPath}`);
}
const byteOrder = header[5];
const machine =
byteOrder === 1
? header.readUInt16LE(18)
: byteOrder === 2
? header.readUInt16BE(18)
: null;
const architecture = new Map([
[62, 'x64'],
[183, 'arm64'],
[40, 'armv7l'],
]).get(machine);
if (!architecture) {
throw new Error(
`Unsupported ELF machine ${String(machine)} in ${binaryPath}.`
);
}
return architecture;
} finally {
fs.closeSync(descriptor);
}
}
function normalizePackageArchitecture(value) {
const normalized = String(value ?? '')
.trim()
.toLowerCase();
const architecture = {
amd64: 'x64',
x86_64: 'x64',
arm64: 'arm64',
aarch64: 'arm64',
armhf: 'armv7l',
armv7h: 'armv7l',
armv7hl: 'armv7l',
}[normalized];
if (!architecture) {
throw new Error(
`Unsupported Linux package architecture: ${
normalized || '<empty>'
}.`
);
}
return architecture;
}
function splitNonEmptyLines(value) {
return String(value ?? '')
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
}
function findPackageInfoFiles(extractionRoot) {
const packageInfoPaths = [];
function visit(directoryPath) {
for (const entry of fs.readdirSync(directoryPath, {
withFileTypes: true,
})) {
const entryPath = path.join(directoryPath, entry.name);
if (entry.isDirectory() && !entry.isSymbolicLink()) {
visit(entryPath);
} else if (entry.isFile() && entry.name === '.PKGINFO') {
packageInfoPaths.push(entryPath);
}
}
}
visit(extractionRoot);
return packageInfoPaths;
}
export function readLinuxArtifactMetadata({
artifactPath,
format,
extractionRoot,
runCommand = runVerifierCommand,
}) {
const run = (command, args) =>
assertCommandSucceeded(command, args, runCommand(command, args));
if (format === 'deb') {
const architectureResult = run('dpkg-deb', [
'--field',
artifactPath,
'Architecture',
]);
const dependencyResult = run('dpkg-deb', [
'--field',
artifactPath,
'Depends',
]);
return {
declaredArch: normalizePackageArchitecture(
architectureResult.stdout
),
dependencies: String(dependencyResult.stdout ?? '')
.split(',')
.map((dependency) => dependency.trim())
.filter(Boolean),
};
}
if (format === 'rpm') {
const architectureResult = run('rpm', [
'-qp',
'--queryformat',
'%{ARCH}\\n',
artifactPath,
]);
const dependencyResult = run('rpm', [
'-qp',
'--requires',
artifactPath,
]);
return {
declaredArch: normalizePackageArchitecture(
architectureResult.stdout
),
dependencies: splitNonEmptyLines(dependencyResult.stdout),
};
}
if (format === 'pacman') {
const packageInfoPaths = findPackageInfoFiles(extractionRoot);
if (packageInfoPaths.length !== 1) {
throw new Error(
`Pacman payload must contain exactly one .PKGINFO; found ${packageInfoPaths.length}.`
);
}
const fields = fs
.readFileSync(packageInfoPaths[0], 'utf8')
.split(/\r?\n/)
.map((line) => line.match(/^([^=]+?)\s*=\s*(.*)$/))
.filter(Boolean)
.map((match) => ({
name: match[1].trim(),
value: match[2].trim(),
}));
const architectures = fields
.filter(({ name }) => name === 'arch')
.map(({ value }) => value);
if (architectures.length !== 1) {
throw new Error(
`Pacman .PKGINFO must declare exactly one architecture; found ${architectures.length}.`
);
}
return {
declaredArch: normalizePackageArchitecture(architectures[0]),
dependencies: fields
.filter(({ name }) => name === 'depend')
.map(({ value }) => value),
};
}
return {
declaredArch: null,
dependencies: [],
};
}
function dependencyMatches(dependency, expected) {
const escapedExpected = expected.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(`^${escapedExpected}(?:$|\\s|[<>=])`).test(
dependency.trim()
);
}
export function validateSystemPackageDependencies(format, dependencies) {
const expectedDependencies = LINUX_SYSTEM_PACKAGE_DEPENDENCIES[format];
if (!expectedDependencies) {
return [];
}
if (!Array.isArray(dependencies)) {
return [
`Linux ${format} package dependency metadata must be an array.`,
];
}
return expectedDependencies.flatMap((expectedDependency) =>
dependencies.some((dependency) =>
dependencyMatches(String(dependency), expectedDependency)
)
? []
: [
`Linux x64 ${format} package must declare ${expectedDependency}.`,
]
);
}
function validateForeignPackageDependencies(format, dependencies) {
const forbiddenDependencies =
LINUX_SYSTEM_PACKAGE_DEPENDENCIES[format] ?? [];
return forbiddenDependencies.flatMap((forbiddenDependency) =>
dependencies.some((dependency) =>
dependencyMatches(String(dependency), forbiddenDependency)
)
? [
`Linux foreign-architecture ${format} package must not declare frame-copy dependency ${forbiddenDependency}.`,
]
: []
);
}
function dependencyFileName(dependencyName) {
return String(dependencyName).replaceAll('\\', '/').split('/').at(-1) ?? '';
}
function resolveElectronBinaryPath(resourceDir, artifactFormat) {
const layout = resolveLinuxLauncherLayout([artifactFormat]);
return path.join(path.dirname(resourceDir), layout.electronBinaryName);
}
function validateElectronIsolation(resourceDir, artifactFormat, elfInspector) {
const errors = [];
const electronPath = resolveElectronBinaryPath(resourceDir, artifactFormat);
const binaries = [
{ label: 'Electron binary', binaryPath: electronPath },
...listElectronShippedLinuxLibraries(resourceDir, {
artifactFormat,
}).map((binaryPath) => ({
label: 'Electron library',
binaryPath,
})),
];
for (const { label, binaryPath } of binaries) {
let stat;
try {
stat = fs.lstatSync(binaryPath);
} catch {
errors.push(`Missing ${label}: ${binaryPath}`);
continue;
}
if (!stat.isFile() || stat.isSymbolicLink()) {
errors.push(`${label} must be a regular file: ${binaryPath}`);
continue;
}
let dynamic;
try {
dynamic = elfInspector(binaryPath);
} catch (error) {
errors.push(
`Unable to inspect ${label} at ${binaryPath}: ${
error instanceof Error ? error.message : String(error)
}`
);
continue;
}
if (!dynamic || !Array.isArray(dynamic.needed)) {
errors.push(
`ELF inspection for ${label} must provide a needed array: ${binaryPath}`
);
continue;
}
const libmpvDependencies = dynamic.needed.filter((dependencyName) =>
LIBMPV_DEPENDENCY_PATTERN.test(dependencyFileName(dependencyName))
);
if (libmpvDependencies.length > 0) {
errors.push(
`${label} must not link libmpv; found ${libmpvDependencies.join(
', '
)} in ${binaryPath}.`
);
}
}
return errors;
}
function defaultElfInspector(binaryPath) {
const result = assertCommandSucceeded(
'readelf',
['-d', binaryPath],
runVerifierCommand('readelf', ['-d', binaryPath])
);
return parseReadelfDynamic(result.stdout);
}
function validateProbeResult(result) {
if (result?.error) {
return [
`Unable to execute Linux frame-copy runtime probe: ${
result.error instanceof Error
? result.error.message
: String(result.error)
}`,
];
}
if (result?.signal) {
return [
`Linux frame-copy runtime probe terminated by signal ${result.signal}.`,
];
}
if (result?.status !== 0) {
const details = [result?.stdout, result?.stderr]
.filter(Boolean)
.join('\n')
.trim();
return [
`Linux frame-copy runtime probe failed with status ${
result?.status ?? 'unknown'
}.${details ? `\n${details}` : ''}`,
];
}
const stdout = result.stdout;
if (typeof stdout !== 'string' || !/^[^\r\n]+\n$/.test(stdout)) {
return [
'Linux frame-copy runtime probe must emit exactly one newline-terminated JSON line.',
];
}
let payload;
try {
payload = JSON.parse(stdout.slice(0, -1));
} catch (error) {
return [
`Linux frame-copy runtime probe emitted invalid JSON: ${
error instanceof Error ? error.message : String(error)
}`,
];
}
if (
!payload ||
typeof payload !== 'object' ||
Array.isArray(payload) ||
JSON.stringify(Object.keys(payload).sort()) !==
JSON.stringify(['libmpv', 'protocol', 'renderApi', 'usable']) ||
payload.protocol !== 1 ||
payload.usable !== true ||
typeof payload.libmpv !== 'string' ||
payload.libmpv.trim() === '' ||
payload.renderApi !== 'egl'
) {
return [
'Linux frame-copy runtime probe did not return protocol 1 usable EGL capability.',
];
}
return [];
}
export function createRuntimeProbeEnvironment({
environment,
nativeDir,
runtimeMode,
}) {
const probeEnvironment = { ...(environment ?? {}) };
for (const variableName of UNSAFE_RUNTIME_PROBE_ENVIRONMENT_VARIABLES) {
delete probeEnvironment[variableName];
}
for (const variableName of Object.keys(probeEnvironment)) {
if (variableName.startsWith('BASH_FUNC_')) {
delete probeEnvironment[variableName];
}
}
if (runtimeMode === 'bundled') {
probeEnvironment.LD_LIBRARY_PATH = path.join(nativeDir, 'lib');
}
return probeEnvironment;
}
function writeVerifierWarning(message) {
process.stderr.write(message);
}
function isProbeTimeoutResult(result) {
const error = result?.error;
return Boolean(
error && typeof error === 'object' && error.code === 'ETIMEDOUT'
);
}
/**
* Executes the bounded packaged helper capability probe.
*
* A hard timeout is the only retried outcome: it proves nothing about the
* payload, and a cold sandbox can spend seconds merely loading libmpv plus the
* EGL/GL/GBM stack. Every other outcome — spawn error, signal, nonzero exit,
* malformed protocol line — is a real capability verdict and is returned
* unchanged after the first attempt. A helper that genuinely hangs still fails
* verification once the bounded attempts are exhausted.
*/
export function runBoundedRuntimeProbe({
probeRunner,
helperPath,
probeEnvironment,
warn = writeVerifierWarning,
}) {
const probeOptions = {
encoding: 'utf8',
env: probeEnvironment,
killSignal: 'SIGKILL',
maxBuffer: RUNTIME_PROBE_MAX_BUFFER_BYTES,
timeout: PACKAGE_VERIFICATION_PROBE_TIMEOUT_MS,
windowsHide: true,
};
let result = probeRunner(helperPath, ['--runtime-probe'], probeOptions);
for (
let attempt = 2;
attempt <= PACKAGE_VERIFICATION_PROBE_MAX_ATTEMPTS &&
isProbeTimeoutResult(result);
attempt += 1
) {
warn(
`Linux frame-copy runtime probe timed out after ${PACKAGE_VERIFICATION_PROBE_TIMEOUT_MS} ms; retrying (attempt ${attempt} of ${PACKAGE_VERIFICATION_PROBE_MAX_ATTEMPTS}).\n`
);
result = probeRunner(helperPath, ['--runtime-probe'], probeOptions);
}
return result;
}
function validateNoEmbeddedMpvNativeArchiveEntries(
resourceDir,
asarListPackage
) {
const asarPath = path.join(resourceDir, 'app.asar');
if (!fs.existsSync(asarPath)) {
return [];
}
let nativeEntries;
try {
nativeEntries = collectEmbeddedMpvNativeArchiveEntries(
asarListPackage(asarPath)
);
} catch (error) {
return [
`Unable to inspect embedded MPV archive ownership in ${asarPath}: ${
error instanceof Error ? error.message : String(error)
}`,
];
}
if (nativeEntries.length === 0) {
return [];
}
return [
[
`Packaged app.asar must not contain embedded MPV native payloads; afterPack exclusively owns the profile-specific unpacked directory in ${asarPath}.`,
...nativeEntries.map((entry) => `- ${entry}`),
].join('\n'),
];
}
export function verifyExtractedLinuxFrameCopyRuntime({
resourceDir,
artifactFormat,
profileName,
packageDependencies = [],
declaredArch = null,
elfInspector = defaultElfInspector,
probeRunner = runVerifierCommand,
environment = process.env,
warn = writeVerifierWarning,
asarListPackage = listAsarPackageEntries,
}) {
const errors = [];
let profile;
try {
profile = resolveLinuxFrameCopyProfile(profileName);
} catch (error) {
return [error instanceof Error ? error.message : String(error)];
}
if (!profile.targets.includes(artifactFormat)) {
return [
`Linux frame-copy profile "${profile.name}" does not include target "${artifactFormat}".`,
];
}
errors.push(
...validateNoEmbeddedMpvNativeArchiveEntries(
resourceDir,
asarListPackage
)
);
const electronPath = resolveElectronBinaryPath(resourceDir, artifactFormat);
let packageArch;
try {
packageArch = readElfArchitecture(electronPath);
} catch (error) {
return [
...errors,
error instanceof Error ? error.message : String(error),
];
}
const foreignArch = packageArch !== 'x64';
if (declaredArch && declaredArch !== packageArch) {
errors.push(
`Package metadata architecture ${declaredArch} does not match Electron ELF architecture ${packageArch}.`
);
}
if (profile.runtimeMode === 'system') {
errors.push(
...(foreignArch
? validateForeignPackageDependencies(
artifactFormat,
packageDependencies
)
: validateSystemPackageDependencies(
artifactFormat,
packageDependencies
))
);
}
errors.push(
...validatePackagedEmbeddedMpv(resourceDir, {
platform: 'linux',
required: true,
foreignArch,
targetArch: packageArch,
profile: profile.name,
targetNames: profile.targets,
artifactFormat,
hostPlatform: 'linux',
executableName: 'iptvnator',
elfInspector,
})
);
errors.push(
...validateElectronIsolation(resourceDir, artifactFormat, elfInspector)
);
if (foreignArch || errors.length > 0) {
return errors;
}
const nativeDir = path.join(
resourceDir,
'app.asar.unpacked',
'electron-backend',
'native'
);
const probeEnvironment = createRuntimeProbeEnvironment({
environment,
nativeDir,
runtimeMode: profile.runtimeMode,
});
const probeResult = runBoundedRuntimeProbe({
probeRunner,
helperPath: path.join(nativeDir, 'iptvnator_mpv_helper'),
probeEnvironment,
warn,
});
errors.push(...validateProbeResult(probeResult));
return errors;
}
export function verifyLinuxFrameCopyArtifact({
artifactPath,
profileName,
runCommand = runVerifierCommand,
extractArtifact = extractLinuxArtifact,
metadataReader = readLinuxArtifactMetadata,
payloadVerifier = verifyExtractedLinuxFrameCopyRuntime,
elfInspector = defaultElfInspector,
probeRunner = runVerifierCommand,
environment = process.env,
}) {
const resolvedArtifactPath = path.resolve(artifactPath);
const artifactStat = fs.lstatSync(resolvedArtifactPath);
if (!artifactStat.isFile() || artifactStat.isSymbolicLink()) {
throw new Error(
`Linux package artifact must be a regular file: ${resolvedArtifactPath}`
);
}
const profile = resolveLinuxFrameCopyProfile(profileName);
const format = detectLinuxArtifactFormat(resolvedArtifactPath);
if (!profile.targets.includes(format)) {
throw new Error(
`Linux frame-copy profile "${profile.name}" does not include target "${format}".`
);
}
const temporaryRoot = fs.mkdtempSync(
path.join(os.tmpdir(), 'iptvnator-linux-package-verifier-')
);
try {
const extractionDestination = path.join(temporaryRoot, 'payload');
const extractionRoot = extractArtifact({
artifactPath: resolvedArtifactPath,
format,
destination: extractionDestination,
runCommand,
});
if (format === 'snap') {
const snapMetadataErrors =
validateExtractedSnapMetadata(extractionRoot);
if (snapMetadataErrors.length > 0) {
throw new Error(
[
`Linux frame-copy package verification failed for ${resolvedArtifactPath}:`,
...snapMetadataErrors.map((error) => `- ${error}`),
].join('\n')
);
}
}
const resourceDir = findExtractedResourceDir(extractionRoot);
const metadata = metadataReader({
artifactPath: resolvedArtifactPath,
format,
extractionRoot,
runCommand,
});
const errors = payloadVerifier({
resourceDir,
artifactFormat: format,
profileName: profile.name,
packageDependencies: metadata.dependencies,
declaredArch: metadata.declaredArch,
elfInspector,
probeRunner,
environment,
});
if (errors.length > 0) {
throw new Error(
[
`Linux frame-copy package verification failed for ${resolvedArtifactPath}:`,
...errors.map((error) => `- ${error}`),
].join('\n')
);
}
const electronPath = resolveElectronBinaryPath(resourceDir, format);
return {
artifactPath: resolvedArtifactPath,
format,
profileName: profile.name,
architecture: readElfArchitecture(electronPath),
};
} finally {
fs.rmSync(temporaryRoot, { recursive: true, force: true });
}
}
function main() {
const options = parseVerifierArguments(process.argv.slice(2));
const result = verifyLinuxFrameCopyArtifact(options);
process.stdout.write(
`Verified ${result.format} ${result.architecture} Linux frame-copy package (${result.profileName}): ${result.artifactPath}\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;
}
}