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
93 lines
4.7 KiB
JavaScript
93 lines
4.7 KiB
JavaScript
'use strict';
|
|
|
|
/*
|
|
* The boot-time dependency check.
|
|
*
|
|
* It exists for the moments nobody is at their best: a rollback that restores an older
|
|
* package.json but not its packages, and a Node upgrade that leaves the native database module
|
|
* compiled against the wrong ABI. Both present as "server will not start", with an error naming a
|
|
* file rather than the action needed.
|
|
*/
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const preflight = require('../lib/preflight-deps');
|
|
|
|
test('a healthy install reports nothing missing and nothing broken', () => {
|
|
assert.deepEqual(preflight.missingDeps(), [], 'this tree should be complete');
|
|
assert.equal(preflight.nativeModuleBroken(), null, 'and the native module should load');
|
|
});
|
|
|
|
test('THE NATIVE CHECK CONSTRUCTS A DATABASE, it does not merely require the module', () => {
|
|
/*
|
|
* better-sqlite3's entry point is plain JavaScript that loads the compiled binding lazily, so
|
|
* `require()` SUCCEEDS under a Node whose ABI the binary was never built for. The first version
|
|
* of this check stopped at require and therefore reported a genuinely broken install — verified
|
|
* against a real Node 18 / Node 20 mismatch — as healthy.
|
|
*
|
|
* Pinned as source because the failure is invisible: the check keeps passing, on every machine
|
|
* where nothing is wrong, right up until the one where something is.
|
|
*/
|
|
const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'preflight-deps.js'), 'utf8');
|
|
const fn = src.slice(src.indexOf('function nativeModuleBroken'), src.indexOf('function run('));
|
|
assert.match(fn, /new Database\(':memory:'\)/,
|
|
'nativeModuleBroken must open a database, or an ABI mismatch goes undetected');
|
|
assert.ok(!/^\s*require\('better-sqlite3'\);\s*$/m.test(fn), 'a bare require is not a load test');
|
|
});
|
|
|
|
test('missingDeps agrees with what is actually on disk', () => {
|
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
const declared = Object.keys(pkg.dependencies || {});
|
|
assert.ok(declared.length > 0, 'the server declares dependencies');
|
|
const reported = preflight.missingDeps();
|
|
for (const name of declared) {
|
|
const present = fs.existsSync(path.join(__dirname, '..', 'node_modules', name, 'package.json'));
|
|
assert.equal(present, !reported.includes(name), `${name}: presence and report disagree`);
|
|
}
|
|
});
|
|
|
|
test('the preflight uses only Node builtins', () => {
|
|
/*
|
|
* It runs BEFORE dependencies are installed, so anything it imported could be the very thing
|
|
* that is missing — and the failure would be the one it exists to prevent, with an extra layer
|
|
* of confusion on top.
|
|
*/
|
|
const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'preflight-deps.js'), 'utf8');
|
|
const requires = [...src.matchAll(/require\('([^']+)'\)/g)].map((m) => m[1]);
|
|
const builtins = new Set(['fs', 'path', 'child_process', 'os', 'crypto', 'util']);
|
|
for (const r of requires) {
|
|
// better-sqlite3 is the thing being TESTED for loadability, not a dependency of this file.
|
|
if (r === 'better-sqlite3') continue;
|
|
/*
|
|
* A `node:`-prefixed specifier can ONLY resolve to a builtin - that is what the prefix is for -
|
|
* so it can never be the missing package this file exists to diagnose.
|
|
*
|
|
* `node:sqlite` arrives here as the fallback-driver probe. Note it is genuinely ABSENT on Node
|
|
* 20 and flagged-off on 22.x, which is exactly why preflight requires it inside a try/catch and
|
|
* treats the throw as an answer rather than an error.
|
|
*/
|
|
if (r.startsWith('node:')) continue;
|
|
assert.ok(builtins.has(r) || r.startsWith('.'), `preflight must not depend on ${r}`);
|
|
}
|
|
});
|
|
|
|
test('it can be turned off for an air-gapped host', () => {
|
|
const src = fs.readFileSync(path.join(__dirname, '..', 'lib', 'preflight-deps.js'), 'utf8');
|
|
assert.match(src, /ST_SKIP_DEP_PREFLIGHT/, 'an operator who manages node_modules must be able to opt out');
|
|
const saved = process.env.ST_SKIP_DEP_PREFLIGHT;
|
|
process.env.ST_SKIP_DEP_PREFLIGHT = '1';
|
|
try { assert.doesNotThrow(() => preflight.preflight(), 'opting out must be a clean no-op'); }
|
|
finally { if (saved === undefined) delete process.env.ST_SKIP_DEP_PREFLIGHT; else process.env.ST_SKIP_DEP_PREFLIGHT = saved; }
|
|
});
|
|
|
|
test('server.js runs the preflight BEFORE requiring anything', () => {
|
|
const src = fs.readFileSync(path.join(__dirname, '..', 'server.js'), 'utf8');
|
|
const preflightAt = src.indexOf("require('./lib/preflight-deps')");
|
|
const firstDep = src.indexOf("require('express')");
|
|
assert.ok(preflightAt > -1, 'server.js must run the preflight');
|
|
assert.ok(preflightAt < firstDep, 'it must come before the first dependency, or it cannot help');
|
|
});
|