Files
iptvnator/tools/packaging/validate-snap-release-boundary.mjs
T
4grayandClaude Fable 5 0adf562177 fix(release): align public Snap verifier with the shipped snap layout
The publish-snap verifier had never run against a real release and
encoded three stale expectations that the tag build's own validators do
not share:

- it required the app under usr/lib/iptvnator inside the snap, while
  Electron Builder's snap target ships the app at the snap root
  (/iptvnator.bin, /resources/**) — the layout the packaged smoke tests
  exercise;
- it validated the source archive's runtime manifest with the raw
  source-build validator, but the archive carries the STAGED manifest
  (origin "vendored-lgpl" + sourceBuildOrigin) written by
  stage-runtime.mjs; the staged envelope is now checked explicitly and
  the remaining fields still go through the shared validator via an
  origin projection;
- it deep-equaled the snap's bundled sourceRuntime against the archive
  manifest, but the snap bundles the builder view (no staging
  envelope); the binding now projects the envelope away first.

Verified end-to-end in a Linux container against the real v0.23.0
release assets: release-snap-assets.cjs verify now passes and emits the
sealed snapshot receipt. Regression tests cover the legacy usr/lib
layout and staged-envelope mismatches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 11:07:02 +02:00

102 lines
2.7 KiB
JavaScript

#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
collectEmbeddedMpvNativeArchiveEntries,
listAsarPackageEntries,
} from './asar-dependency-closure.mjs';
import { validateExtractedSnapMetadata } from './verify-linux-frame-copy-runtime.mjs';
const scriptPath = fileURLToPath(import.meta.url);
const SNAP_RELEASE_BOUNDARY_SCHEMA_VERSION = 1;
export function validateExtractedSnapReleaseBoundary(
extractionRoot,
{ asarListPackage = listAsarPackageEntries } = {}
) {
const errors = [...validateExtractedSnapMetadata(extractionRoot)];
const asarPath = path.join(extractionRoot, 'resources', 'app.asar');
let asarStat;
try {
asarStat = fs.lstatSync(asarPath);
} catch {
errors.push(
`Public-release Snap must contain its canonical app.asar: ${asarPath}`
);
return errors;
}
if (
!asarStat.isFile() ||
asarStat.isSymbolicLink() ||
asarStat.size === 0
) {
errors.push(
`Public-release Snap app.asar must be a non-empty regular file: ${asarPath}`
);
return errors;
}
let nativeEntries;
try {
nativeEntries = collectEmbeddedMpvNativeArchiveEntries(
asarListPackage(asarPath)
);
} catch (error) {
errors.push(
`Unable to inspect public-release Snap app.asar at ${asarPath}: ${
error instanceof Error ? error.message : String(error)
}`
);
return errors;
}
if (nativeEntries.length > 0) {
errors.push(
`Public-release Snap app.asar must not contain embedded MPV native payloads: ${nativeEntries.join(
', '
)}`
);
}
return errors;
}
function main() {
const args = process.argv.slice(2);
if (args.length !== 1 || args[0].length === 0) {
throw new Error(
'Usage: validate-snap-release-boundary.mjs <extracted-snap-root>'
);
}
const errors = validateExtractedSnapReleaseBoundary(path.resolve(args[0]));
process.stdout.write(
`${JSON.stringify({
schemaVersion: SNAP_RELEASE_BOUNDARY_SCHEMA_VERSION,
errors,
})}\n`
);
}
function isMainModule() {
if (!process.argv[1]) {
return false;
}
try {
return fs.realpathSync(process.argv[1]) === fs.realpathSync(scriptPath);
} catch {
return false;
}
}
if (isMainModule()) {
try {
main();
} catch (error) {
process.stderr.write(
`${error instanceof Error ? error.message : String(error)}\n`
);
process.exitCode = 1;
}
}