mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-19 08:33:56 -06:00
TWO CHANGES, together because they touch the same packager hunks. 1. THE DRIVER. The BrightSign package used to be MANUFACTURED. scripts/build-server-zip.sh dropped better-sqlite3 from package.json and then installed db/sqlite-compat.js into node_modules under that name, so every require resolved to the façade. It worked — and it shipped a database layer that no test had ever executed. That is the same shape as the TELEMETRY_COLLECTOR TDZ crash that took production down while 1676 tests and four CI jobs were green: a build-time rewrite cannot be tested by the build that performs it. db/sqlite-driver.js now decides at runtime: the native driver when it loads, the node:sqlite façade otherwise. One artifact, one code path, and — the point — both branches reachable from a test. ST_SQLITE_DRIVER=node runs the entire suite the way a player runs it, and a new CI job does exactly that on Node 24 with --omit=optional so the fallback is reached the same way it is on hardware, not by an env var alone. better-sqlite3 becomes an optionalDependency, so a host with no compiler installs cleanly and falls back rather than failing. preflight-deps stops trying to rebuild a native module on a host that has no toolchain and a working built-in driver — on a player that was a five-minute node-gyp failure ending in a server that never started. Asking for the native driver BY NAME (ST_SQLITE_DRIVER=better-sqlite3) still fails loudly, because a production box that has lost its native module is broken and should say so rather than quietly running something else. ⚠️ NODE 24 IN PRACTICE. node:sqlite is unflagged only from 23.4; on the 22.x line it needs --experimental-sqlite and on 20.x it does not exist. So the code probes rather than comparing versions, the player package pins engines >=24, and the built-in cases skip on the Node 20 CI job rather than failing there. Verified on Node 24, both drivers, full suite: better-sqlite3 1762 pass / 0 fail node:sqlite 1762 pass / 0 fail and the built payload resolves node:sqlite with no better-sqlite3 present at all. 2. THE LICENCE. The ffprobe/ffmpeg binaries added in the previous commit are LGPL 2.1 and statically linked, so the licence text has to travel WITH them — a link on a website is not the copy the licence asks to accompany the work. The packager now copies COPYING.LGPLv2.1 and a build README into bin/, and refuses to build if the licence is missing. legal/third-party.html gains an LGPL section with the written offer required by section 6 for static linking, and the exact configure line. It also drops Sharp, which that page still listed although #263 removed it, and names what actually does the image work now (jimp, @jsquash/webp, @jsquash/avif). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014kfhrUPit5MCqxeTQyqr56
61 lines
2.9 KiB
JavaScript
61 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* Report-only audit: find playlist_items whose zone_id is NOT a zone in the
|
|
* device's ACTIVE layout — i.e. orphaned cross-layout assignments. Un-patched
|
|
* players silently drop these; patched players (this branch) route them to the
|
|
* largest zone and emit a "zone" device-log warning. This script only REPORTS;
|
|
* it never mutates. Run it against a COPY of the prod DB.
|
|
*
|
|
* node scripts/find-orphan-zone-items.js [path/to/remote_display.db]
|
|
*
|
|
* Exit code is always 0 (it's a report); the count is printed.
|
|
*/
|
|
const path = require('path');
|
|
const { Database } = require(path.join(__dirname, '..', 'server', 'db', 'sqlite-driver.js'));
|
|
|
|
const dbPath = process.argv[2] || path.join(__dirname, '..', 'server', 'db', 'remote_display.db');
|
|
const db = new Database(dbPath, { readonly: true });
|
|
|
|
// One row per (device, zoned item). A playlist shared by N devices is checked
|
|
// against EACH device's layout, since the same item can be valid for one device
|
|
// and orphaned for another.
|
|
const rows = db.prepare(`
|
|
SELECT d.id AS device_id, d.name AS device_name,
|
|
d.layout_id AS device_layout, dl.name AS device_layout_name,
|
|
pi.id AS item_id, pi.zone_id,
|
|
c.filename, c.mime_type,
|
|
lz.layout_id AS zone_layout, zl.name AS zone_layout_name, lz.name AS zone_name
|
|
FROM devices d
|
|
JOIN playlist_items pi ON pi.playlist_id = d.playlist_id
|
|
LEFT JOIN content c ON c.id = pi.content_id
|
|
LEFT JOIN layout_zones lz ON lz.id = pi.zone_id
|
|
LEFT JOIN layouts dl ON dl.id = d.layout_id
|
|
LEFT JOIN layouts zl ON zl.id = lz.layout_id
|
|
WHERE pi.zone_id IS NOT NULL
|
|
`).all();
|
|
|
|
// Orphan = the item's zone doesn't exist any more, OR it belongs to a different
|
|
// layout than the device is actually rendering.
|
|
const orphans = rows.filter(r => !r.zone_layout || r.zone_layout !== r.device_layout);
|
|
|
|
if (!orphans.length) {
|
|
console.log(`No orphaned zone assignments found in ${dbPath}.`);
|
|
db.close();
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log(`Found ${orphans.length} orphaned playlist_item(s) in ${dbPath}`);
|
|
console.log(`(zone_id references a zone that is NOT in the device's active layout):\n`);
|
|
for (const o of orphans) {
|
|
const sid = s => (s || '').slice(0, 8);
|
|
const where = o.zone_layout
|
|
? `zone "${o.zone_name}" lives in layout "${o.zone_layout_name}" (${sid(o.zone_layout)})`
|
|
: `zone_id no longer exists`;
|
|
console.log(` device "${o.device_name}" (${sid(o.device_id)}) active layout "${o.device_layout_name || '—'}" (${sid(o.device_layout)})`);
|
|
console.log(` item #${o.item_id} ${o.filename || '?'} [${o.mime_type || '?'}] zone_id=${sid(o.zone_id)} -> ${where}`);
|
|
}
|
|
console.log(`\nReport only — nothing changed. Un-patched players drop these; patched players`);
|
|
console.log(`route them to the largest zone and log a "zone" warning. Use the hardening`);
|
|
console.log(`(remap-on-duplicate / validate-on-assign) to stop new ones being created.`);
|
|
db.close();
|