#!/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');