Add patch script to wire printer.js to the native QL module

Replaces the brother_ql subprocess call in printBadge with printPng, and
swaps available() for a real reachability check. Uses brace matching rather
than literal body matching so it survives formatting differences, refuses to
run if the anchors are missing, is idempotent, and writes a .bak first.

The badge layout is untouched.
This commit is contained in:
2026-09-07 14:50:34 +10:00
parent 8d2111ff41
commit cb7bcff93e
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env node
/**
* One-shot patch: point printer.js at the native QL protocol module instead of
* shelling out to the Python brother_ql CLI.
*
* node scripts/wire-native-printing.mjs # patch
* node scripts/wire-native-printing.mjs --check # report only, change nothing
*
* Safe to run twice — it detects an already-patched file and stops.
* Writes printer.js.bak next to the original before touching anything.
*
* Only the imports and the two functions at the bottom change. The badge
* layout (drawBadge / renderBadgePng) is left exactly as it is.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const here = path.dirname(fileURLToPath(import.meta.url));
const target = path.resolve(here, '..', 'src', 'printer.js');
const checkOnly = process.argv.includes('--check');
function fail(message) {
console.error(`\n FAILED: ${message}\n`);
console.error(' Nothing was changed. Patch printer.js by hand instead.\n');
process.exit(1);
}
/**
* Find a top-level function and return its full text by matching braces.
* Formatting-independent, unlike matching the whole body literally.
*/
function extractFunction(source, signature) {
const start = source.indexOf(signature);
if (start === -1) return null;
let depth = 0;
let i = source.indexOf('{', start);
if (i === -1) return null;
for (; i < source.length; i++) {
if (source[i] === '{') depth++;
else if (source[i] === '}') {
depth--;
if (depth === 0) {
return { start, end: i + 1, text: source.slice(start, i + 1) };
}
}
}
return null;
}
if (!fs.existsSync(target)) fail(`${target} does not exist. Run this from the repo root.`);
let source = fs.readFileSync(target, 'utf8');
const original = source;
if (source.includes("from './printing/ql-print.js'")) {
console.log('\n Already patched — printer.js imports the native module. Nothing to do.\n');
process.exit(0);
}
const changes = [];
/* -- 1. Import the native module ---------------------------------- */
const importAnchor = "import { photoAbsolutePath } from './photos.js';";
if (!source.includes(importAnchor)) fail('could not find the photos.js import');
source = source.replace(
importAnchor,
`${importAnchor}\nimport { printPng, printerHealth } from './printing/ql-print.js';`
);
changes.push('added the ql-print import');
/* -- 2. Replace printBadge ---------------------------------------- */
const printBadge = extractFunction(source, 'export async function printBadge(visit, site)');
if (!printBadge) fail('could not locate printBadge');
if (!printBadge.text.includes('runBrotherQl')) {
fail('printBadge does not call runBrotherQl — it may already have been changed');
}
const newPrintBadge = `export async function printBadge(visit, site) {
if (!isConfigured(site)) throw new Error('Server printing is not turned on for this site.');
const png = await renderBadgePng(visit, site);
const port = Number(site.printer_port) || 9100;
const target = \`tcp://\${site.printer_host}:\${port}\`;
// The roll decides whether the job is two-colour, not the accent setting.
// A DK-22251 roll refuses a monochrome job even when the badge has no red
// on it, so labelFor() is passed straight through and ql-print maps it.
const result = await printPng(png, {
host: site.printer_host,
port,
label: labelFor(site),
});
note(site.id, result.ok, result.message);
if (!result.ok) throw new Error(result.message);
// confirmed is false when the printer accepted the bytes but never said the
// label came out. Over the network that is the normal case, not a fault.
return { ok: true, confirmed: Boolean(result.confirmed), target };
}`;
source =
source.slice(0, printBadge.start) + newPrintBadge + source.slice(printBadge.end);
changes.push('printBadge now uses printPng');
/* -- 3. Replace available ----------------------------------------- */
const available = extractFunction(source, 'export function available()');
if (available) {
const newAvailable = `export function available() {
// Kept for callers that only ask "can this server print at all?". The
// external brother_ql binary is no longer involved, so the answer is always
// yes; use health(site) to ask about a specific printer.
return Promise.resolve(true);
}
/**
* Reachability and, where the printer will say, roll state.
*
* ready is tri-state: true, false, or null meaning "reachable but it won't
* tell us". Null is the normal answer over the network — show it as unknown
* in the admin console rather than green or red.
*/
export function health(site) {
return printerHealth({
host: site?.printer_host,
port: Number(site?.printer_port) || 9100,
label: labelFor(site),
});
}`;
source =
source.slice(0, available.start) + newAvailable + source.slice(available.end);
changes.push('available() no longer probes for a binary; added health(site)');
} else {
console.warn(' note: available() not found, skipping that step');
}
/* -- 4. Flag the now-dead subprocess code ------------------------- */
for (const name of ['function explainPrintError', 'function runBrotherQl']) {
if (source.includes(name)) {
changes.push(`${name.split(' ')[1]}() is now unused and can be deleted`);
}
}
/* -- Report / write ----------------------------------------------- */
console.log('\n Changes to src/printer.js:');
for (const c of changes) console.log(` - ${c}`);
if (checkOnly) {
console.log('\n --check given, nothing written.\n');
process.exit(0);
}
if (source === original) fail('nothing actually changed');
fs.writeFileSync(`${target}.bak`, original);
fs.writeFileSync(target, source);
console.log(`\n Wrote ${target}`);
console.log(` Backup at ${target}.bak`);
console.log('\n Next: docker compose build && docker compose up -d\n');