mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
Third slice of the release-notes pipeline (#1256 format+generator, #1257 CI gate): release screenshots become reproducible and provably mock-only. The v0.20 capture script was single-use (hard-coded slugs, paths, hero) and fail-open: a lost IPTVNATOR_E2E_DATA_DIR silently fell back to the user's real ~/.iptvnator database, `...process.env` leaked ambient TMDB keys and proxies, nothing gated network access, and no frame content was ever validated. Each hole leaks real playlists, credentials, or copyrighted artwork into published screenshots without a single signal. New pipeline: - tools/release/screenshots.manifest.json — declarative shots (slug, title, named setup steps, themes). Adding a feature shot = one manifest entry. - capture-release-screenshots.ts — orchestrator; output goes to apps/website/public/blog/<release>/screenshots/<slug>-<theme>.png, release slug derived from package.json (or --release), --only/--theme filters. - capture-app-driver.ts / capture-navigation.ts — launch, seeding, theme, and the named-action vocabulary; actions are order-independent (every portal action starts from the dashboard). - screenshot-guards.mjs — the fail-closed policy, pure and unit-tested: G1 the real database is snapshotted (sha256+mtime) before launch and must be byte-identical after; the isolated DB must actually exist G2 the app receives an allowlisted environment, never ...process.env G3 deny-by-default network gate; known app-level calls (GitHub update check) are answered by local stubs; any other blocked request fails the run — a silently-blocked TMDB call would leave a frame that looks broken rather than unsafe G4 every frame is scanned before capture: external img/background URLs, credential-shaped text, MAC addresses, non-localhost m3u8 references G5 TMDB enrichment asserted disabled via the renderer's IndexedDB Any violation deletes every frame captured in the run and exits non-zero. The guards paid for themselves on the first live run: G3 caught the mock server redirecting stream endpoints to a public demo HLS (test-streams.mux.dev) — meaning earlier hand-run captures could embed third-party video frames. The M3U shot now deliberately captures the groups layout without starting playback. `.changes` validation now cross-checks `screenshot:` slugs against the manifest, so a note cannot reference an image the capture run never produces. Verified end-to-end: 10/10 shots (5 slugs × dark/light) captured against dist build + xtream-mock-server, frames visually inspected (fictional titles/artwork only), guard-violation paths exercised live. 67 unit tests in release-tools, lint green, script files within the repo size limit. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
/**
|
|
* G5: proof that TMDB enrichment stays off for the capture profile, so no
|
|
* licensed poster or still can reach a published screenshot.
|
|
*/
|
|
|
|
import type { Page } from '@playwright/test';
|
|
|
|
/**
|
|
* G5: settings live in the renderer's IndexedDB (ngx-pwa StorageMap). A
|
|
* fresh profile has no entry, which means the TMDB defaults (disabled,
|
|
* empty key) apply. Read-only assertion — if a future change flips the
|
|
* default or seeds a key, the run stops before a licensed poster can render.
|
|
*/
|
|
export async function assertTmdbDisabled(page: Page): Promise<void> {
|
|
const tmdb = await page.evaluate(
|
|
() =>
|
|
new Promise<{ enabled?: boolean; apiKey?: string } | null>(
|
|
(resolve) => {
|
|
const request = indexedDB.open('ngStorage');
|
|
|
|
request.onerror = () => resolve(null);
|
|
request.onsuccess = () => {
|
|
const db = request.result;
|
|
|
|
if (!db.objectStoreNames.contains('localStorage')) {
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
const get = db
|
|
.transaction('localStorage')
|
|
.objectStore('localStorage')
|
|
.get('settings');
|
|
|
|
get.onerror = () => resolve(null);
|
|
get.onsuccess = () =>
|
|
resolve(
|
|
(get.result as { tmdb?: { enabled?: boolean; apiKey?: string } })
|
|
?.tmdb ?? null
|
|
);
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
assertSync(
|
|
!tmdb?.enabled && !tmdb?.apiKey,
|
|
`G5 failed: TMDB enrichment is active in the capture profile (${JSON.stringify(tmdb)})`
|
|
);
|
|
}
|
|
|
|
function assertSync(condition: unknown, message: string): asserts condition {
|
|
if (!condition) {
|
|
throw new Error(message);
|
|
}
|
|
}
|