@bobfrankston/brother-label 1.1.7 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +124 -38
- package/cli.js.map +1 -1
- package/cli.ts +126 -34
- package/mdns.d.ts +33 -0
- package/mdns.d.ts.map +1 -0
- package/mdns.js +146 -0
- package/mdns.js.map +1 -0
- package/mdns.ts +145 -0
- package/package.json +8 -4
package/cli.js
CHANGED
|
@@ -33,6 +33,7 @@ const BOOLEAN = [
|
|
|
33
33
|
{ names: ["-t"], key: "text" },
|
|
34
34
|
{ names: ["-c", "-clip"], key: "clip" },
|
|
35
35
|
{ names: ["-no-wait", "-nowait"], key: "nowait" },
|
|
36
|
+
{ names: ["-mdns"], key: "mdns" },
|
|
36
37
|
{ names: ["-help", "-h", "-?"], key: "help" },
|
|
37
38
|
{ names: ["-version", "-v"], key: "version" },
|
|
38
39
|
];
|
|
@@ -45,7 +46,8 @@ Usage:
|
|
|
45
46
|
brother-print [options] <file> Print html/txt/image file (auto-detected)
|
|
46
47
|
brother-print -clip [options] Print clipboard contents (image or text)
|
|
47
48
|
brother-print -text <v> -qr <v> ... Print ordered text/qr segments
|
|
48
|
-
brother-print list
|
|
49
|
+
brother-print list [-mdns] List Brother printers and status
|
|
50
|
+
(-mdns also discovers network printers)
|
|
49
51
|
brother-print status [-p <name>] Show printer status
|
|
50
52
|
brother-print config Show / set defaults
|
|
51
53
|
|
|
@@ -67,9 +69,14 @@ Options:
|
|
|
67
69
|
-no-wait Fail immediately if printer is offline (default: wait)
|
|
68
70
|
-timeout <secs> Max wait time when printer is offline
|
|
69
71
|
-interval <secs> Polling interval while waiting (default 2)
|
|
72
|
+
-mdns With "list": also discover network printers via mDNS
|
|
70
73
|
-help Show this help
|
|
71
74
|
-version Show version`);
|
|
72
75
|
}
|
|
76
|
+
/** Expected user-facing error: prints one line, no log file, no stack. */
|
|
77
|
+
class UserError extends Error {
|
|
78
|
+
isUserError = true;
|
|
79
|
+
}
|
|
73
80
|
async function main() {
|
|
74
81
|
const argv = preprocessSingleQuotes(process.argv.slice(2));
|
|
75
82
|
let parsed;
|
|
@@ -94,9 +101,12 @@ async function main() {
|
|
|
94
101
|
return;
|
|
95
102
|
}
|
|
96
103
|
try {
|
|
104
|
+
if (typeof parsed.opts.printer === "string") {
|
|
105
|
+
parsed.opts.printer = await resolvePrinter(parsed.opts.printer);
|
|
106
|
+
}
|
|
97
107
|
switch (parsed.command) {
|
|
98
108
|
case "list":
|
|
99
|
-
await cmdList();
|
|
109
|
+
await cmdList(parsed.opts);
|
|
100
110
|
return;
|
|
101
111
|
case "status":
|
|
102
112
|
await cmdStatus(parsed.opts);
|
|
@@ -108,10 +118,58 @@ async function main() {
|
|
|
108
118
|
await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);
|
|
109
119
|
}
|
|
110
120
|
catch (e) {
|
|
121
|
+
if (e?.isUserError) {
|
|
122
|
+
console.error(`Error: ${e.message}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
111
125
|
reportError(e);
|
|
112
126
|
process.exit(1);
|
|
113
127
|
}
|
|
114
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Resolve a -p value to an installed Windows queue name. Accepts:
|
|
131
|
+
* #N 1-based index into the list shown by `brother-label list`
|
|
132
|
+
* <exact> Full queue name (case-sensitive match shortcut)
|
|
133
|
+
* <substr> Case-insensitive substring against name or port (e.g. "ql820",
|
|
134
|
+
* "172.20.1.165", "BRW4CD577B59B6A"). Errors if ambiguous.
|
|
135
|
+
*/
|
|
136
|
+
async function resolvePrinter(value) {
|
|
137
|
+
const { listPrinters: coreListPrinters } = await import("@bobfrankston/label-core");
|
|
138
|
+
const all = await coreListPrinters(/brother|^pt-|^ql-/i);
|
|
139
|
+
const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));
|
|
140
|
+
if (sorted.length === 0) {
|
|
141
|
+
throw new UserError(`No Brother printers installed. Run 'brother-label list' to verify.`);
|
|
142
|
+
}
|
|
143
|
+
const idx = value.match(/^#(\d+)$/);
|
|
144
|
+
if (idx) {
|
|
145
|
+
const n = parseInt(idx[1], 10);
|
|
146
|
+
if (n < 1 || n > sorted.length) {
|
|
147
|
+
throw new UserError(`-p ${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);
|
|
148
|
+
}
|
|
149
|
+
return sorted[n - 1].name;
|
|
150
|
+
}
|
|
151
|
+
const exact = sorted.find(p => p.name === value);
|
|
152
|
+
if (exact)
|
|
153
|
+
return exact.name;
|
|
154
|
+
const lower = value.toLowerCase();
|
|
155
|
+
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
156
|
+
const target = norm(value);
|
|
157
|
+
const matches = sorted.filter(p => {
|
|
158
|
+
const name = p.name.toLowerCase();
|
|
159
|
+
const port = (p.portName || "").toLowerCase();
|
|
160
|
+
if (name.includes(lower) || port.includes(lower))
|
|
161
|
+
return true;
|
|
162
|
+
// Normalized fallback: "ql820" matches "Brother QL-820NWB"
|
|
163
|
+
return norm(p.name).includes(target) || norm(p.portName || "").includes(target);
|
|
164
|
+
});
|
|
165
|
+
if (matches.length === 1)
|
|
166
|
+
return matches[0].name;
|
|
167
|
+
if (matches.length === 0) {
|
|
168
|
+
throw new UserError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);
|
|
169
|
+
}
|
|
170
|
+
const names = matches.map(p => p.name).join(", ");
|
|
171
|
+
throw new UserError(`'${value}' is ambiguous — matches: ${names}`);
|
|
172
|
+
}
|
|
115
173
|
/**
|
|
116
174
|
* Print an error with full context: message, cause chain (Node's fetch and
|
|
117
175
|
* other newer APIs put the actual cause in error.cause), and stack on -v.
|
|
@@ -160,58 +218,86 @@ function reportError(e) {
|
|
|
160
218
|
wroteFile = true;
|
|
161
219
|
}
|
|
162
220
|
catch { /* fallback */ }
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
else
|
|
221
|
+
if (wroteFile) {
|
|
222
|
+
console.error(`Error: ${tempPath}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
168
225
|
console.error(dump);
|
|
226
|
+
}
|
|
169
227
|
if (process.env.BROTHER_PRINT_DEBUG) {
|
|
170
228
|
console.error("--- full report (BROTHER_PRINT_DEBUG enabled) ---");
|
|
171
229
|
console.error(dump);
|
|
172
230
|
}
|
|
173
231
|
}
|
|
174
|
-
async function cmdList() {
|
|
232
|
+
async function cmdList(opts) {
|
|
175
233
|
const printers = await listPrinters();
|
|
176
|
-
if (printers.length === 0) {
|
|
177
|
-
console.log("No Brother printers found.");
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
234
|
/* Brother's listPrinters returns just { name } (legacy shape). To get
|
|
181
235
|
* portName/driverName for the diagnostic line, query label-core's
|
|
182
236
|
* coreListPrinters too. */
|
|
183
237
|
const { listPrinters: coreListPrinters } = await import("@bobfrankston/label-core");
|
|
184
238
|
const all = await coreListPrinters(/brother|^pt-|^ql-/i);
|
|
185
239
|
const byName = new Map(all.map(p => [p.name, p]));
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
240
|
+
if (printers.length === 0) {
|
|
241
|
+
console.log("No installed Brother printers.");
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));
|
|
245
|
+
console.log(`Brother printers (${sorted.length}):`);
|
|
246
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
247
|
+
const p = sorted[i];
|
|
248
|
+
const info = byName.get(p.name);
|
|
249
|
+
let statusStr = "?";
|
|
250
|
+
try {
|
|
251
|
+
const s = await brotherPrinter.getStatus(p.name);
|
|
252
|
+
statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
statusStr = `status error: ${e.message}`;
|
|
256
|
+
}
|
|
257
|
+
const port = info ? shortenPort(info.portName) : "(unknown)";
|
|
258
|
+
const driver = info?.driverName ?? "(unknown)";
|
|
259
|
+
console.log(` #${i + 1} ${p.name}`);
|
|
260
|
+
if (info?.comment)
|
|
261
|
+
console.log(` description: ${info.comment}`);
|
|
262
|
+
if (info?.location)
|
|
263
|
+
console.log(` location: ${info.location}`);
|
|
264
|
+
console.log(` port: ${port}`);
|
|
265
|
+
if (info?.portName && info.portName !== port) {
|
|
266
|
+
console.log(` full port: ${info.portName}`);
|
|
267
|
+
}
|
|
268
|
+
console.log(` driver: ${driver}`);
|
|
269
|
+
if (info?.shareName && info.shareName !== p.name) {
|
|
270
|
+
console.log(` share name: ${info.shareName}`);
|
|
271
|
+
}
|
|
272
|
+
console.log(` status: ${statusStr}`);
|
|
198
273
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
console.log(` full port: ${info.portName}`);
|
|
274
|
+
}
|
|
275
|
+
if (opts.mdns) {
|
|
276
|
+
const { discoverPdlPrinters, isBrother, modelFromTxt, matchesInstalledPort } = await import("./mdns.js");
|
|
277
|
+
console.log("");
|
|
278
|
+
console.log("Browsing mDNS for _pdl-datastream._tcp (3s)...");
|
|
279
|
+
const found = (await discoverPdlPrinters()).filter(isBrother);
|
|
280
|
+
if (found.length === 0) {
|
|
281
|
+
console.log("No Brother printers responded via mDNS.");
|
|
282
|
+
return;
|
|
209
283
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
const installedPorts = all.map(p => p.portName);
|
|
285
|
+
const networkOnly = found.filter(m => !installedPorts.some(pn => matchesInstalledPort(m, pn)));
|
|
286
|
+
const matched = found.filter(m => installedPorts.some(pn => matchesInstalledPort(m, pn)));
|
|
287
|
+
console.log(`mDNS responders (${found.length}; ${matched.length} matched, ${networkOnly.length} network-only):`);
|
|
288
|
+
let n = 1;
|
|
289
|
+
for (const m of [...matched, ...networkOnly]) {
|
|
290
|
+
const isNetOnly = networkOnly.includes(m);
|
|
291
|
+
const tag = isNetOnly ? " [network only]" : " [installed]";
|
|
292
|
+
const model = modelFromTxt(m) ?? "(unknown model)";
|
|
293
|
+
console.log(` #${n++} ${m.instanceName}${tag}`);
|
|
294
|
+
console.log(` model: ${model}`);
|
|
295
|
+
console.log(` hostname: ${m.hostname.replace(/\.$/, "")}`);
|
|
296
|
+
if (m.addresses.length > 0) {
|
|
297
|
+
console.log(` address: ${m.addresses.join(", ")}`);
|
|
298
|
+
}
|
|
299
|
+
console.log(` port: ${m.port}`);
|
|
213
300
|
}
|
|
214
|
-
console.log(` status: ${statusStr}`);
|
|
215
301
|
}
|
|
216
302
|
}
|
|
217
303
|
/**
|
package/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Brother Label Printer CLI\n * Thin wrapper around api.ts using shared CLI primitives from label-core.\n */\n\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport {\n preprocessSingleQuotes,\n parseArgs,\n} from \"@bobfrankston/label-core\";\nimport type {\n PrintOptions as CorePrintOptions,\n PrinterStatus,\n Segment,\n ValuedOptionSpec,\n BooleanFlagSpec,\n} from \"@bobfrankston/label-core\";\nimport {\n print,\n render,\n renderSegments,\n printSegments,\n getConfig,\n setConfig,\n getConfigPath,\n listPrinters,\n detectTapeSize,\n brotherPrinter,\n} from \"./api.js\";\nimport type { TapeSize, PrintOptions, SegmentOptions } from \"./api.js\";\n\nconst VERSION = \"1.1.0\";\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\n\nfunction parseTape(value: string): TapeSize {\n const num = parseInt(value.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(num)) {\n throw new Error(`Invalid tape size: ${value}. Valid: ${VALID_TAPES.join(\", \")}`);\n }\n return num;\n}\n\nconst VALUED: ValuedOptionSpec[] = [\n { names: [\"-tape\"], key: \"tape\" },\n { names: [\"-p\", \"-printer\"], key: \"printer\" },\n { names: [\"-o\", \"-output\"], key: \"output\" },\n { names: [\"-a\", \"-aspect\"], key: \"aspect\" },\n { names: [\"-ht\", \"-height\"], key: \"height\" },\n { names: [\"-s\", \"-space\"], key: \"space\" },\n { names: [\"-timeout\"], key: \"timeout\" },\n { names: [\"-interval\"], key: \"interval\" },\n];\n\nconst BOOLEAN: BooleanFlagSpec[] = [\n { names: [\"-w\", \"-html\"], key: \"html\" },\n { names: [\"-i\", \"-image\"], key: \"image\" },\n { names: [\"-t\"], key: \"text\" },\n { names: [\"-c\", \"-clip\"], key: \"clip\" },\n { names: [\"-no-wait\", \"-nowait\"], key: \"nowait\" },\n { names: [\"-help\", \"-h\", \"-?\"], key: \"help\" },\n { names: [\"-version\", \"-v\"], key: \"version\" },\n];\n\nconst SUBCOMMANDS = [\"list\", \"status\", \"config\"];\n\nfunction showHelp(): void {\n console.log(`Brother Label Printer CLI v${VERSION}\n\nUsage:\n brother-print [options] <text> Print text\n brother-print [options] <file> Print html/txt/image file (auto-detected)\n brother-print -clip [options] Print clipboard contents (image or text)\n brother-print -text <v> -qr <v> ... Print ordered text/qr segments\n brother-print list List Brother printers and status\n brother-print status [-p <name>] Show printer status\n brother-print config Show / set defaults\n\nTape size is auto-detected from the printer. Use -tape to override.\nSingle quotes can wrap arguments: '7\"' 'line1\\\\nline2'\n\nOptions:\n -tape <size> Tape size: 6, 9, 12, 18, 24 (mm) [auto-detected]\n -p, -printer <name> Printer queue name\n -o, -output <file> Save PNG to file instead of printing\n -a, -aspect <r> Aspect ratio width:height for HTML (e.g. 3.5:2)\n -ht, -height <size> Text height: 12mm, .5in, or 50% (of tape height)\n -s, -space <size> Space between segments: 12px, 1mm, .2in\n -t, -text Force input as literal text (-text <v> for segments)\n -qr <data> QR code segment\n -w, -html Force input as HTML file path\n -i, -image Force input as image file path\n -c, -clip Read content from clipboard (image preferred, then text)\n -no-wait Fail immediately if printer is offline (default: wait)\n -timeout <secs> Max wait time when printer is offline\n -interval <secs> Polling interval while waiting (default 2)\n -help Show this help\n -version Show version`);\n}\n\nasync function main(): Promise<void> {\n const argv = preprocessSingleQuotes(process.argv.slice(2));\n let parsed;\n try {\n parsed = parseArgs(argv, VALUED, BOOLEAN, SUBCOMMANDS);\n } catch (e: any) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n\n if (parsed.unknown.length > 0) {\n console.error(`Unknown option(s): ${parsed.unknown.join(\", \")}`);\n console.error(`Run \"brother-print -help\" for usage.`);\n process.exit(1);\n }\n\n if (parsed.opts.help) { showHelp(); return; }\n if (parsed.opts.version) { console.log(VERSION); return; }\n\n try {\n switch (parsed.command) {\n case \"list\": await cmdList(); return;\n case \"status\": await cmdStatus(parsed.opts); return;\n case \"config\": await cmdConfig(parsed.opts); return;\n }\n await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);\n } catch (e: any) {\n reportError(e);\n process.exit(1);\n }\n}\n\n/**\n * Print an error with full context: message, cause chain (Node's fetch and\n * other newer APIs put the actual cause in error.cause), and stack on -v.\n */\n/**\n * Report an error: print a one-line summary to stderr and write the full\n * cause-chain (with stacks, codes, paths) to a temp file. Avoids dumping\n * walls of error text into the user's terminal. Set BROTHER_PRINT_DEBUG=1\n * to also include the full chain inline.\n */\nfunction reportError(e: any): void {\n const lines: string[] = [];\n lines.push(`# brother-print error report`);\n lines.push(`# timestamp: ${new Date().toISOString()}`);\n lines.push(`# argv: ${process.argv.join(\" \")}`);\n lines.push(`# cwd: ${process.cwd()}`);\n lines.push(`# pid: ${process.pid}`);\n lines.push(\"\");\n\n let cur = e;\n let depth = 0;\n while (cur) {\n const prefix = depth === 0 ? \"Error\" : \"Caused by\";\n const name = cur.name && cur.name !== \"Error\" ? ` ${cur.name}` : \"\";\n const code = cur.code ? ` [${cur.code}]` : \"\";\n const msg = cur.message ?? String(cur);\n lines.push(`${prefix}${name}${code}: ${msg}`);\n if (cur.path) lines.push(` path: ${cur.path}`);\n if (cur.input) lines.push(` input: ${cur.input}`);\n if (cur.stack) lines.push(String(cur.stack));\n lines.push(\"\");\n cur = cur.cause;\n depth++;\n if (depth > 8) { lines.push(\"... (cause chain truncated at 8 levels)\"); break; }\n }\n\n const dump = lines.join(\"\\n\");\n const tempPath = path.join(os.tmpdir(), `brother-print-error-${Date.now()}-${process.pid}.log`);\n let wroteFile = false;\n try { fs.writeFileSync(tempPath, dump); wroteFile = true; } catch { /* fallback */ }\n\n const topMsg = e?.message ?? String(e);\n console.error(`Error: ${topMsg}`);\n if (wroteFile) console.error(`Details: ${tempPath}`);\n else console.error(dump);\n if (process.env.BROTHER_PRINT_DEBUG) {\n console.error(\"--- full report (BROTHER_PRINT_DEBUG enabled) ---\");\n console.error(dump);\n }\n}\n\nasync function cmdList(): Promise<void> {\n const printers = await listPrinters();\n if (printers.length === 0) {\n console.log(\"No Brother printers found.\");\n return;\n }\n /* Brother's listPrinters returns just { name } (legacy shape). To get\n * portName/driverName for the diagnostic line, query label-core's\n * coreListPrinters too. */\n const { listPrinters: coreListPrinters } = await import(\"@bobfrankston/label-core\");\n const all = await coreListPrinters(/brother|^pt-|^ql-/i);\n const byName = new Map(all.map(p => [p.name, p]));\n\n const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));\n console.log(`Brother printers (${sorted.length}):`);\n for (let i = 0; i < sorted.length; i++) {\n const p = sorted[i];\n const info = byName.get(p.name);\n let statusStr = \"?\";\n try {\n const s = await brotherPrinter.getStatus(p.name);\n statusStr = s.online ? \"online\" : `offline (${s.statusText}${s.error ? \", \" + s.error : \"\"})`;\n } catch (e: any) {\n statusStr = `status error: ${e.message}`;\n }\n const port = info ? shortenPort(info.portName) : \"(unknown)\";\n const driver = info?.driverName ?? \"(unknown)\";\n console.log(` #${i + 1} ${p.name}`);\n if (info?.comment) console.log(` description: ${info.comment}`);\n if (info?.location) console.log(` location: ${info.location}`);\n console.log(` port: ${port}`);\n if (info?.portName && info.portName !== port) {\n console.log(` full port: ${info.portName}`);\n }\n console.log(` driver: ${driver}`);\n if (info?.shareName && info.shareName !== p.name) {\n console.log(` share name: ${info.shareName}`);\n }\n console.log(` status: ${statusStr}`);\n }\n}\n\n/**\n * Pull a short identifier out of a Windows printer port: \"... on hostname\"\n * → hostname; raw IP/hostname kept as-is; USB ports kept as-is.\n */\nfunction shortenPort(portName: string): string {\n if (!portName) return \"(unknown)\";\n const onMatch = portName.match(/\\bon\\s+(\\S+)\\s*$/i);\n if (onMatch) return onMatch[1];\n const hostMatch = portName.match(/\\b(\\d{1,3}(?:\\.\\d{1,3}){3}|[\\w-]+\\.local|[\\w-]+\\.[\\w.-]+)\\b/);\n if (hostMatch) return hostMatch[1];\n return portName;\n}\n\nasync function cmdStatus(opts: Record<string, string | boolean>): Promise<void> {\n const name = (opts.printer as string) || undefined;\n const s = await brotherPrinter.getStatus(name);\n console.log(`Printer: ${s.name}`);\n console.log(`Online: ${s.online ? \"yes\" : \"no\"}`);\n console.log(`Status: ${s.statusText}`);\n if (s.error) console.log(`Error: ${s.error}`);\n console.log(`Queue: ${s.queueLength} job(s)`);\n if (s.workOffline) console.log(`Work offline flag: true`);\n}\n\nasync function cmdConfig(opts: Record<string, string | boolean>): Promise<void> {\n const hasUpdate = !!(opts.tape || opts.printer);\n if (!hasUpdate) {\n const cfg = getConfig();\n console.log(`Configuration:`);\n console.log(` File: ${getConfigPath()}`);\n console.log(` Default tape: ${cfg.defaultTape ? cfg.defaultTape + \"mm\" : \"(not set)\"}`);\n console.log(` Default printer: ${cfg.defaultPrinter ?? \"(not set)\"}`);\n console.log(\"\\nValid tape sizes: 6, 9, 12, 18, 24\");\n return;\n }\n if (opts.tape) {\n const t = parseTape(opts.tape as string);\n setConfig({ defaultTape: t });\n console.log(`Default tape set to: ${t}mm`);\n }\n if (opts.printer) {\n setConfig({ defaultPrinter: opts.printer as string });\n console.log(`Default printer set to: ${opts.printer}`);\n }\n}\n\nasync function resolveTape(opts: Record<string, string | boolean>): Promise<TapeSize> {\n const explicit = opts.tape ? parseTape(opts.tape as string) : null;\n const detected = await detectTapeSize(opts.printer as string | undefined);\n if (explicit) {\n if (detected && detected !== explicit) {\n console.warn(`Warning: -tape ${explicit}mm specified but printer reports ${detected}mm tape loaded`);\n }\n return explicit;\n }\n if (detected) return detected;\n return getConfig().defaultTape ?? 24;\n}\n\nasync function cmdPrint(\n opts: Record<string, string | boolean>,\n segments: Segment[],\n inputs: string[]\n): Promise<void> {\n const tape = await resolveTape(opts);\n\n // If there are 2+ positional inputs, treat each as a text segment.\n // (A single positional input is handled below via classifyInput so it can\n // auto-detect file paths.)\n if (inputs.length > 1 || (inputs.length >= 1 && segments.length > 0)) {\n for (const inp of inputs) {\n segments.push({ type: \"text\", value: inp });\n }\n inputs.length = 0;\n }\n\n const baseOpts = {\n printer: opts.printer as string | undefined,\n wait: !opts.nowait,\n waitTimeoutMs: opts.timeout ? Math.round(parseFloat(opts.timeout as string) * 1000) : undefined,\n waitIntervalMs: opts.interval ? Math.round(parseFloat(opts.interval as string) * 1000) : undefined,\n onWaiting: makeWaitCallback(),\n log: (msg: string) => console.log(msg),\n };\n\n // Multi-segment mode: explicit -text/-qr (>=1 segment), with positional joined in\n if (segments.length > 1 || (segments.length === 1 && inputs.length === 0)) {\n const segOpts: SegmentOptions = {\n ...baseOpts,\n tape,\n textHeight: opts.height as string | undefined,\n space: opts.space as string | undefined,\n };\n if (opts.output) {\n const buffer = await renderSegments(segments, tape, opts.height as string | undefined, opts.space as string | undefined);\n fs.writeFileSync(opts.output as string, buffer);\n console.log(`Saved to ${opts.output}`);\n } else {\n await printSegments(segments, segOpts);\n console.log(`Printed ${segments.length} segment(s) on ${tape}mm tape`);\n }\n return;\n }\n\n // Single content op\n const printOpts: PrintOptions = {\n ...baseOpts,\n tape,\n aspect: opts.aspect as string | undefined,\n textHeight: opts.height as string | undefined,\n };\n\n if (opts.clip) {\n printOpts.clip = true;\n } else if (inputs.length === 1) {\n Object.assign(printOpts, classifyInput(inputs[0], opts));\n } else if (inputs.length === 0 && segments.length === 0) {\n showHelp();\n return;\n }\n\n if (opts.output) {\n const buffer = await render(printOpts);\n fs.writeFileSync(opts.output as string, buffer);\n console.log(`Saved to ${opts.output}`);\n return;\n }\n await print(printOpts);\n console.log(`Printed on ${tape}mm tape`);\n}\n\nfunction classifyInput(input: string, opts: Record<string, string | boolean>): Partial<CorePrintOptions> {\n if (opts.text) return { text: input };\n if (opts.html) return { htmlPath: path.resolve(input) };\n if (opts.image) return { imagePath: path.resolve(input) };\n\n const lower = input.toLowerCase();\n if (lower.endsWith(\".html\") || lower.endsWith(\".htm\")) return { htmlPath: path.resolve(input) };\n if (lower.endsWith(\".txt\")) return { textFile: path.resolve(input) };\n if (/\\.(png|jpg|jpeg|bmp|gif)$/i.test(lower)) return { imagePath: path.resolve(input) };\n if (fs.existsSync(input)) {\n const ext = path.extname(lower);\n if (ext === \".html\" || ext === \".htm\") return { htmlPath: path.resolve(input) };\n return { textFile: path.resolve(input) };\n }\n return { text: input };\n}\n\nfunction makeWaitCallback() {\n let lastReport = 0;\n return (status: PrinterStatus, elapsedMs: number, alternatives: PrinterStatus[]) => {\n const secs = Math.floor(elapsedMs / 1000);\n if (secs - lastReport < 5) return;\n lastReport = secs;\n const altStr = alternatives.length > 0\n ? ` (online alternatives: ${alternatives.map(a => a.name).join(\", \")})`\n : \"\";\n console.log(`[brother-print] still waiting for ${status.name} (${status.statusText}${status.error ? \", \" + status.error : \"\"}); ${secs}s elapsed${altStr}`);\n };\n}\n\nmain();\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Brother Label Printer CLI\n * Thin wrapper around api.ts using shared CLI primitives from label-core.\n */\n\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport {\n preprocessSingleQuotes,\n parseArgs,\n} from \"@bobfrankston/label-core\";\nimport type {\n PrintOptions as CorePrintOptions,\n PrinterStatus,\n Segment,\n ValuedOptionSpec,\n BooleanFlagSpec,\n} from \"@bobfrankston/label-core\";\nimport {\n print,\n render,\n renderSegments,\n printSegments,\n getConfig,\n setConfig,\n getConfigPath,\n listPrinters,\n detectTapeSize,\n brotherPrinter,\n} from \"./api.js\";\nimport type { TapeSize, PrintOptions, SegmentOptions } from \"./api.js\";\n\nconst VERSION = \"1.1.0\";\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\n\nfunction parseTape(value: string): TapeSize {\n const num = parseInt(value.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(num)) {\n throw new Error(`Invalid tape size: ${value}. Valid: ${VALID_TAPES.join(\", \")}`);\n }\n return num;\n}\n\nconst VALUED: ValuedOptionSpec[] = [\n { names: [\"-tape\"], key: \"tape\" },\n { names: [\"-p\", \"-printer\"], key: \"printer\" },\n { names: [\"-o\", \"-output\"], key: \"output\" },\n { names: [\"-a\", \"-aspect\"], key: \"aspect\" },\n { names: [\"-ht\", \"-height\"], key: \"height\" },\n { names: [\"-s\", \"-space\"], key: \"space\" },\n { names: [\"-timeout\"], key: \"timeout\" },\n { names: [\"-interval\"], key: \"interval\" },\n];\n\nconst BOOLEAN: BooleanFlagSpec[] = [\n { names: [\"-w\", \"-html\"], key: \"html\" },\n { names: [\"-i\", \"-image\"], key: \"image\" },\n { names: [\"-t\"], key: \"text\" },\n { names: [\"-c\", \"-clip\"], key: \"clip\" },\n { names: [\"-no-wait\", \"-nowait\"], key: \"nowait\" },\n { names: [\"-mdns\"], key: \"mdns\" },\n { names: [\"-help\", \"-h\", \"-?\"], key: \"help\" },\n { names: [\"-version\", \"-v\"], key: \"version\" },\n];\n\nconst SUBCOMMANDS = [\"list\", \"status\", \"config\"];\n\nfunction showHelp(): void {\n console.log(`Brother Label Printer CLI v${VERSION}\n\nUsage:\n brother-print [options] <text> Print text\n brother-print [options] <file> Print html/txt/image file (auto-detected)\n brother-print -clip [options] Print clipboard contents (image or text)\n brother-print -text <v> -qr <v> ... Print ordered text/qr segments\n brother-print list [-mdns] List Brother printers and status\n (-mdns also discovers network printers)\n brother-print status [-p <name>] Show printer status\n brother-print config Show / set defaults\n\nTape size is auto-detected from the printer. Use -tape to override.\nSingle quotes can wrap arguments: '7\"' 'line1\\\\nline2'\n\nOptions:\n -tape <size> Tape size: 6, 9, 12, 18, 24 (mm) [auto-detected]\n -p, -printer <name> Printer queue name\n -o, -output <file> Save PNG to file instead of printing\n -a, -aspect <r> Aspect ratio width:height for HTML (e.g. 3.5:2)\n -ht, -height <size> Text height: 12mm, .5in, or 50% (of tape height)\n -s, -space <size> Space between segments: 12px, 1mm, .2in\n -t, -text Force input as literal text (-text <v> for segments)\n -qr <data> QR code segment\n -w, -html Force input as HTML file path\n -i, -image Force input as image file path\n -c, -clip Read content from clipboard (image preferred, then text)\n -no-wait Fail immediately if printer is offline (default: wait)\n -timeout <secs> Max wait time when printer is offline\n -interval <secs> Polling interval while waiting (default 2)\n -mdns With \"list\": also discover network printers via mDNS\n -help Show this help\n -version Show version`);\n}\n\n/** Expected user-facing error: prints one line, no log file, no stack. */\nclass UserError extends Error {\n readonly isUserError = true;\n}\n\nasync function main(): Promise<void> {\n const argv = preprocessSingleQuotes(process.argv.slice(2));\n let parsed;\n try {\n parsed = parseArgs(argv, VALUED, BOOLEAN, SUBCOMMANDS);\n } catch (e: any) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n\n if (parsed.unknown.length > 0) {\n console.error(`Unknown option(s): ${parsed.unknown.join(\", \")}`);\n console.error(`Run \"brother-print -help\" for usage.`);\n process.exit(1);\n }\n\n if (parsed.opts.help) { showHelp(); return; }\n if (parsed.opts.version) { console.log(VERSION); return; }\n\n try {\n if (typeof parsed.opts.printer === \"string\") {\n parsed.opts.printer = await resolvePrinter(parsed.opts.printer);\n }\n switch (parsed.command) {\n case \"list\": await cmdList(parsed.opts); return;\n case \"status\": await cmdStatus(parsed.opts); return;\n case \"config\": await cmdConfig(parsed.opts); return;\n }\n await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);\n } catch (e: any) {\n if (e?.isUserError) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n reportError(e);\n process.exit(1);\n }\n}\n\n/**\n * Resolve a -p value to an installed Windows queue name. Accepts:\n * #N 1-based index into the list shown by `brother-label list`\n * <exact> Full queue name (case-sensitive match shortcut)\n * <substr> Case-insensitive substring against name or port (e.g. \"ql820\",\n * \"172.20.1.165\", \"BRW4CD577B59B6A\"). Errors if ambiguous.\n */\nasync function resolvePrinter(value: string): Promise<string> {\n const { listPrinters: coreListPrinters } = await import(\"@bobfrankston/label-core\");\n const all = await coreListPrinters(/brother|^pt-|^ql-/i);\n const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));\n\n if (sorted.length === 0) {\n throw new UserError(`No Brother printers installed. Run 'brother-label list' to verify.`);\n }\n\n const idx = value.match(/^#(\\d+)$/);\n if (idx) {\n const n = parseInt(idx[1], 10);\n if (n < 1 || n > sorted.length) {\n throw new UserError(`-p ${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);\n }\n return sorted[n - 1].name;\n }\n\n const exact = sorted.find(p => p.name === value);\n if (exact) return exact.name;\n\n const lower = value.toLowerCase();\n const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n const target = norm(value);\n const matches = sorted.filter(p => {\n const name = p.name.toLowerCase();\n const port = (p.portName || \"\").toLowerCase();\n if (name.includes(lower) || port.includes(lower)) return true;\n // Normalized fallback: \"ql820\" matches \"Brother QL-820NWB\"\n return norm(p.name).includes(target) || norm(p.portName || \"\").includes(target);\n });\n if (matches.length === 1) return matches[0].name;\n if (matches.length === 0) {\n throw new UserError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);\n }\n const names = matches.map(p => p.name).join(\", \");\n throw new UserError(`'${value}' is ambiguous — matches: ${names}`);\n}\n\n/**\n * Print an error with full context: message, cause chain (Node's fetch and\n * other newer APIs put the actual cause in error.cause), and stack on -v.\n */\n/**\n * Report an error: print a one-line summary to stderr and write the full\n * cause-chain (with stacks, codes, paths) to a temp file. Avoids dumping\n * walls of error text into the user's terminal. Set BROTHER_PRINT_DEBUG=1\n * to also include the full chain inline.\n */\nfunction reportError(e: any): void {\n const lines: string[] = [];\n lines.push(`# brother-print error report`);\n lines.push(`# timestamp: ${new Date().toISOString()}`);\n lines.push(`# argv: ${process.argv.join(\" \")}`);\n lines.push(`# cwd: ${process.cwd()}`);\n lines.push(`# pid: ${process.pid}`);\n lines.push(\"\");\n\n let cur = e;\n let depth = 0;\n while (cur) {\n const prefix = depth === 0 ? \"Error\" : \"Caused by\";\n const name = cur.name && cur.name !== \"Error\" ? ` ${cur.name}` : \"\";\n const code = cur.code ? ` [${cur.code}]` : \"\";\n const msg = cur.message ?? String(cur);\n lines.push(`${prefix}${name}${code}: ${msg}`);\n if (cur.path) lines.push(` path: ${cur.path}`);\n if (cur.input) lines.push(` input: ${cur.input}`);\n if (cur.stack) lines.push(String(cur.stack));\n lines.push(\"\");\n cur = cur.cause;\n depth++;\n if (depth > 8) { lines.push(\"... (cause chain truncated at 8 levels)\"); break; }\n }\n\n const dump = lines.join(\"\\n\");\n const tempPath = path.join(os.tmpdir(), `brother-print-error-${Date.now()}-${process.pid}.log`);\n let wroteFile = false;\n try { fs.writeFileSync(tempPath, dump); wroteFile = true; } catch { /* fallback */ }\n\n if (wroteFile) {\n console.error(`Error: ${tempPath}`);\n } else {\n console.error(dump);\n }\n if (process.env.BROTHER_PRINT_DEBUG) {\n console.error(\"--- full report (BROTHER_PRINT_DEBUG enabled) ---\");\n console.error(dump);\n }\n}\n\nasync function cmdList(opts: Record<string, string | boolean>): Promise<void> {\n const printers = await listPrinters();\n /* Brother's listPrinters returns just { name } (legacy shape). To get\n * portName/driverName for the diagnostic line, query label-core's\n * coreListPrinters too. */\n const { listPrinters: coreListPrinters } = await import(\"@bobfrankston/label-core\");\n const all = await coreListPrinters(/brother|^pt-|^ql-/i);\n const byName = new Map(all.map(p => [p.name, p]));\n\n if (printers.length === 0) {\n console.log(\"No installed Brother printers.\");\n } else {\n const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));\n console.log(`Brother printers (${sorted.length}):`);\n for (let i = 0; i < sorted.length; i++) {\n const p = sorted[i];\n const info = byName.get(p.name);\n let statusStr = \"?\";\n try {\n const s = await brotherPrinter.getStatus(p.name);\n statusStr = s.online ? \"online\" : `offline (${s.statusText}${s.error ? \", \" + s.error : \"\"})`;\n } catch (e: any) {\n statusStr = `status error: ${e.message}`;\n }\n const port = info ? shortenPort(info.portName) : \"(unknown)\";\n const driver = info?.driverName ?? \"(unknown)\";\n console.log(` #${i + 1} ${p.name}`);\n if (info?.comment) console.log(` description: ${info.comment}`);\n if (info?.location) console.log(` location: ${info.location}`);\n console.log(` port: ${port}`);\n if (info?.portName && info.portName !== port) {\n console.log(` full port: ${info.portName}`);\n }\n console.log(` driver: ${driver}`);\n if (info?.shareName && info.shareName !== p.name) {\n console.log(` share name: ${info.shareName}`);\n }\n console.log(` status: ${statusStr}`);\n }\n }\n\n if (opts.mdns) {\n const { discoverPdlPrinters, isBrother, modelFromTxt, matchesInstalledPort } =\n await import(\"./mdns.js\");\n console.log(\"\");\n console.log(\"Browsing mDNS for _pdl-datastream._tcp (3s)...\");\n const found = (await discoverPdlPrinters()).filter(isBrother);\n if (found.length === 0) {\n console.log(\"No Brother printers responded via mDNS.\");\n return;\n }\n const installedPorts = all.map(p => p.portName);\n const networkOnly = found.filter(m => !installedPorts.some(pn => matchesInstalledPort(m, pn)));\n const matched = found.filter(m => installedPorts.some(pn => matchesInstalledPort(m, pn)));\n\n console.log(`mDNS responders (${found.length}; ${matched.length} matched, ${networkOnly.length} network-only):`);\n let n = 1;\n for (const m of [...matched, ...networkOnly]) {\n const isNetOnly = networkOnly.includes(m);\n const tag = isNetOnly ? \" [network only]\" : \" [installed]\";\n const model = modelFromTxt(m) ?? \"(unknown model)\";\n console.log(` #${n++} ${m.instanceName}${tag}`);\n console.log(` model: ${model}`);\n console.log(` hostname: ${m.hostname.replace(/\\.$/, \"\")}`);\n if (m.addresses.length > 0) {\n console.log(` address: ${m.addresses.join(\", \")}`);\n }\n console.log(` port: ${m.port}`);\n }\n }\n}\n\n/**\n * Pull a short identifier out of a Windows printer port: \"... on hostname\"\n * → hostname; raw IP/hostname kept as-is; USB ports kept as-is.\n */\nfunction shortenPort(portName: string): string {\n if (!portName) return \"(unknown)\";\n const onMatch = portName.match(/\\bon\\s+(\\S+)\\s*$/i);\n if (onMatch) return onMatch[1];\n const hostMatch = portName.match(/\\b(\\d{1,3}(?:\\.\\d{1,3}){3}|[\\w-]+\\.local|[\\w-]+\\.[\\w.-]+)\\b/);\n if (hostMatch) return hostMatch[1];\n return portName;\n}\n\nasync function cmdStatus(opts: Record<string, string | boolean>): Promise<void> {\n const name = (opts.printer as string) || undefined;\n const s = await brotherPrinter.getStatus(name);\n console.log(`Printer: ${s.name}`);\n console.log(`Online: ${s.online ? \"yes\" : \"no\"}`);\n console.log(`Status: ${s.statusText}`);\n if (s.error) console.log(`Error: ${s.error}`);\n console.log(`Queue: ${s.queueLength} job(s)`);\n if (s.workOffline) console.log(`Work offline flag: true`);\n}\n\nasync function cmdConfig(opts: Record<string, string | boolean>): Promise<void> {\n const hasUpdate = !!(opts.tape || opts.printer);\n if (!hasUpdate) {\n const cfg = getConfig();\n console.log(`Configuration:`);\n console.log(` File: ${getConfigPath()}`);\n console.log(` Default tape: ${cfg.defaultTape ? cfg.defaultTape + \"mm\" : \"(not set)\"}`);\n console.log(` Default printer: ${cfg.defaultPrinter ?? \"(not set)\"}`);\n console.log(\"\\nValid tape sizes: 6, 9, 12, 18, 24\");\n return;\n }\n if (opts.tape) {\n const t = parseTape(opts.tape as string);\n setConfig({ defaultTape: t });\n console.log(`Default tape set to: ${t}mm`);\n }\n if (opts.printer) {\n setConfig({ defaultPrinter: opts.printer as string });\n console.log(`Default printer set to: ${opts.printer}`);\n }\n}\n\nasync function resolveTape(opts: Record<string, string | boolean>): Promise<TapeSize> {\n const explicit = opts.tape ? parseTape(opts.tape as string) : null;\n const detected = await detectTapeSize(opts.printer as string | undefined);\n if (explicit) {\n if (detected && detected !== explicit) {\n console.warn(`Warning: -tape ${explicit}mm specified but printer reports ${detected}mm tape loaded`);\n }\n return explicit;\n }\n if (detected) return detected;\n return getConfig().defaultTape ?? 24;\n}\n\nasync function cmdPrint(\n opts: Record<string, string | boolean>,\n segments: Segment[],\n inputs: string[]\n): Promise<void> {\n const tape = await resolveTape(opts);\n\n // If there are 2+ positional inputs, treat each as a text segment.\n // (A single positional input is handled below via classifyInput so it can\n // auto-detect file paths.)\n if (inputs.length > 1 || (inputs.length >= 1 && segments.length > 0)) {\n for (const inp of inputs) {\n segments.push({ type: \"text\", value: inp });\n }\n inputs.length = 0;\n }\n\n const baseOpts = {\n printer: opts.printer as string | undefined,\n wait: !opts.nowait,\n waitTimeoutMs: opts.timeout ? Math.round(parseFloat(opts.timeout as string) * 1000) : undefined,\n waitIntervalMs: opts.interval ? Math.round(parseFloat(opts.interval as string) * 1000) : undefined,\n onWaiting: makeWaitCallback(),\n log: (msg: string) => console.log(msg),\n };\n\n // Multi-segment mode: explicit -text/-qr (>=1 segment), with positional joined in\n if (segments.length > 1 || (segments.length === 1 && inputs.length === 0)) {\n const segOpts: SegmentOptions = {\n ...baseOpts,\n tape,\n textHeight: opts.height as string | undefined,\n space: opts.space as string | undefined,\n };\n if (opts.output) {\n const buffer = await renderSegments(segments, tape, opts.height as string | undefined, opts.space as string | undefined);\n fs.writeFileSync(opts.output as string, buffer);\n console.log(`Saved to ${opts.output}`);\n } else {\n await printSegments(segments, segOpts);\n console.log(`Printed ${segments.length} segment(s) on ${tape}mm tape`);\n }\n return;\n }\n\n // Single content op\n const printOpts: PrintOptions = {\n ...baseOpts,\n tape,\n aspect: opts.aspect as string | undefined,\n textHeight: opts.height as string | undefined,\n };\n\n if (opts.clip) {\n printOpts.clip = true;\n } else if (inputs.length === 1) {\n Object.assign(printOpts, classifyInput(inputs[0], opts));\n } else if (inputs.length === 0 && segments.length === 0) {\n showHelp();\n return;\n }\n\n if (opts.output) {\n const buffer = await render(printOpts);\n fs.writeFileSync(opts.output as string, buffer);\n console.log(`Saved to ${opts.output}`);\n return;\n }\n await print(printOpts);\n console.log(`Printed on ${tape}mm tape`);\n}\n\nfunction classifyInput(input: string, opts: Record<string, string | boolean>): Partial<CorePrintOptions> {\n if (opts.text) return { text: input };\n if (opts.html) return { htmlPath: path.resolve(input) };\n if (opts.image) return { imagePath: path.resolve(input) };\n\n const lower = input.toLowerCase();\n if (lower.endsWith(\".html\") || lower.endsWith(\".htm\")) return { htmlPath: path.resolve(input) };\n if (lower.endsWith(\".txt\")) return { textFile: path.resolve(input) };\n if (/\\.(png|jpg|jpeg|bmp|gif)$/i.test(lower)) return { imagePath: path.resolve(input) };\n if (fs.existsSync(input)) {\n const ext = path.extname(lower);\n if (ext === \".html\" || ext === \".htm\") return { htmlPath: path.resolve(input) };\n return { textFile: path.resolve(input) };\n }\n return { text: input };\n}\n\nfunction makeWaitCallback() {\n let lastReport = 0;\n return (status: PrinterStatus, elapsedMs: number, alternatives: PrinterStatus[]) => {\n const secs = Math.floor(elapsedMs / 1000);\n if (secs - lastReport < 5) return;\n lastReport = secs;\n const altStr = alternatives.length > 0\n ? ` (online alternatives: ${alternatives.map(a => a.name).join(\", \")})`\n : \"\";\n console.log(`[brother-print] still waiting for ${status.name} (${status.statusText}${status.error ? \", \" + status.error : \"\"}); ${secs}s elapsed${altStr}`);\n };\n}\n\nmain();\n"]}
|
package/cli.ts
CHANGED
|
@@ -60,6 +60,7 @@ const BOOLEAN: BooleanFlagSpec[] = [
|
|
|
60
60
|
{ names: ["-t"], key: "text" },
|
|
61
61
|
{ names: ["-c", "-clip"], key: "clip" },
|
|
62
62
|
{ names: ["-no-wait", "-nowait"], key: "nowait" },
|
|
63
|
+
{ names: ["-mdns"], key: "mdns" },
|
|
63
64
|
{ names: ["-help", "-h", "-?"], key: "help" },
|
|
64
65
|
{ names: ["-version", "-v"], key: "version" },
|
|
65
66
|
];
|
|
@@ -74,7 +75,8 @@ Usage:
|
|
|
74
75
|
brother-print [options] <file> Print html/txt/image file (auto-detected)
|
|
75
76
|
brother-print -clip [options] Print clipboard contents (image or text)
|
|
76
77
|
brother-print -text <v> -qr <v> ... Print ordered text/qr segments
|
|
77
|
-
brother-print list
|
|
78
|
+
brother-print list [-mdns] List Brother printers and status
|
|
79
|
+
(-mdns also discovers network printers)
|
|
78
80
|
brother-print status [-p <name>] Show printer status
|
|
79
81
|
brother-print config Show / set defaults
|
|
80
82
|
|
|
@@ -96,10 +98,16 @@ Options:
|
|
|
96
98
|
-no-wait Fail immediately if printer is offline (default: wait)
|
|
97
99
|
-timeout <secs> Max wait time when printer is offline
|
|
98
100
|
-interval <secs> Polling interval while waiting (default 2)
|
|
101
|
+
-mdns With "list": also discover network printers via mDNS
|
|
99
102
|
-help Show this help
|
|
100
103
|
-version Show version`);
|
|
101
104
|
}
|
|
102
105
|
|
|
106
|
+
/** Expected user-facing error: prints one line, no log file, no stack. */
|
|
107
|
+
class UserError extends Error {
|
|
108
|
+
readonly isUserError = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
103
111
|
async function main(): Promise<void> {
|
|
104
112
|
const argv = preprocessSingleQuotes(process.argv.slice(2));
|
|
105
113
|
let parsed;
|
|
@@ -120,18 +128,71 @@ async function main(): Promise<void> {
|
|
|
120
128
|
if (parsed.opts.version) { console.log(VERSION); return; }
|
|
121
129
|
|
|
122
130
|
try {
|
|
131
|
+
if (typeof parsed.opts.printer === "string") {
|
|
132
|
+
parsed.opts.printer = await resolvePrinter(parsed.opts.printer);
|
|
133
|
+
}
|
|
123
134
|
switch (parsed.command) {
|
|
124
|
-
case "list": await cmdList(); return;
|
|
135
|
+
case "list": await cmdList(parsed.opts); return;
|
|
125
136
|
case "status": await cmdStatus(parsed.opts); return;
|
|
126
137
|
case "config": await cmdConfig(parsed.opts); return;
|
|
127
138
|
}
|
|
128
139
|
await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);
|
|
129
140
|
} catch (e: any) {
|
|
141
|
+
if (e?.isUserError) {
|
|
142
|
+
console.error(`Error: ${e.message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
130
145
|
reportError(e);
|
|
131
146
|
process.exit(1);
|
|
132
147
|
}
|
|
133
148
|
}
|
|
134
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Resolve a -p value to an installed Windows queue name. Accepts:
|
|
152
|
+
* #N 1-based index into the list shown by `brother-label list`
|
|
153
|
+
* <exact> Full queue name (case-sensitive match shortcut)
|
|
154
|
+
* <substr> Case-insensitive substring against name or port (e.g. "ql820",
|
|
155
|
+
* "172.20.1.165", "BRW4CD577B59B6A"). Errors if ambiguous.
|
|
156
|
+
*/
|
|
157
|
+
async function resolvePrinter(value: string): Promise<string> {
|
|
158
|
+
const { listPrinters: coreListPrinters } = await import("@bobfrankston/label-core");
|
|
159
|
+
const all = await coreListPrinters(/brother|^pt-|^ql-/i);
|
|
160
|
+
const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));
|
|
161
|
+
|
|
162
|
+
if (sorted.length === 0) {
|
|
163
|
+
throw new UserError(`No Brother printers installed. Run 'brother-label list' to verify.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const idx = value.match(/^#(\d+)$/);
|
|
167
|
+
if (idx) {
|
|
168
|
+
const n = parseInt(idx[1], 10);
|
|
169
|
+
if (n < 1 || n > sorted.length) {
|
|
170
|
+
throw new UserError(`-p ${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);
|
|
171
|
+
}
|
|
172
|
+
return sorted[n - 1].name;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const exact = sorted.find(p => p.name === value);
|
|
176
|
+
if (exact) return exact.name;
|
|
177
|
+
|
|
178
|
+
const lower = value.toLowerCase();
|
|
179
|
+
const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
180
|
+
const target = norm(value);
|
|
181
|
+
const matches = sorted.filter(p => {
|
|
182
|
+
const name = p.name.toLowerCase();
|
|
183
|
+
const port = (p.portName || "").toLowerCase();
|
|
184
|
+
if (name.includes(lower) || port.includes(lower)) return true;
|
|
185
|
+
// Normalized fallback: "ql820" matches "Brother QL-820NWB"
|
|
186
|
+
return norm(p.name).includes(target) || norm(p.portName || "").includes(target);
|
|
187
|
+
});
|
|
188
|
+
if (matches.length === 1) return matches[0].name;
|
|
189
|
+
if (matches.length === 0) {
|
|
190
|
+
throw new UserError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);
|
|
191
|
+
}
|
|
192
|
+
const names = matches.map(p => p.name).join(", ");
|
|
193
|
+
throw new UserError(`'${value}' is ambiguous — matches: ${names}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
135
196
|
/**
|
|
136
197
|
* Print an error with full context: message, cause chain (Node's fetch and
|
|
137
198
|
* other newer APIs put the actual cause in error.cause), and stack on -v.
|
|
@@ -173,22 +234,19 @@ function reportError(e: any): void {
|
|
|
173
234
|
let wroteFile = false;
|
|
174
235
|
try { fs.writeFileSync(tempPath, dump); wroteFile = true; } catch { /* fallback */ }
|
|
175
236
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
237
|
+
if (wroteFile) {
|
|
238
|
+
console.error(`Error: ${tempPath}`);
|
|
239
|
+
} else {
|
|
240
|
+
console.error(dump);
|
|
241
|
+
}
|
|
180
242
|
if (process.env.BROTHER_PRINT_DEBUG) {
|
|
181
243
|
console.error("--- full report (BROTHER_PRINT_DEBUG enabled) ---");
|
|
182
244
|
console.error(dump);
|
|
183
245
|
}
|
|
184
246
|
}
|
|
185
247
|
|
|
186
|
-
async function cmdList(): Promise<void> {
|
|
248
|
+
async function cmdList(opts: Record<string, string | boolean>): Promise<void> {
|
|
187
249
|
const printers = await listPrinters();
|
|
188
|
-
if (printers.length === 0) {
|
|
189
|
-
console.log("No Brother printers found.");
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
250
|
/* Brother's listPrinters returns just { name } (legacy shape). To get
|
|
193
251
|
* portName/driverName for the diagnostic line, query label-core's
|
|
194
252
|
* coreListPrinters too. */
|
|
@@ -196,32 +254,66 @@ async function cmdList(): Promise<void> {
|
|
|
196
254
|
const all = await coreListPrinters(/brother|^pt-|^ql-/i);
|
|
197
255
|
const byName = new Map(all.map(p => [p.name, p]));
|
|
198
256
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
let
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
statusStr =
|
|
208
|
-
|
|
209
|
-
|
|
257
|
+
if (printers.length === 0) {
|
|
258
|
+
console.log("No installed Brother printers.");
|
|
259
|
+
} else {
|
|
260
|
+
const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));
|
|
261
|
+
console.log(`Brother printers (${sorted.length}):`);
|
|
262
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
263
|
+
const p = sorted[i];
|
|
264
|
+
const info = byName.get(p.name);
|
|
265
|
+
let statusStr = "?";
|
|
266
|
+
try {
|
|
267
|
+
const s = await brotherPrinter.getStatus(p.name);
|
|
268
|
+
statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
|
|
269
|
+
} catch (e: any) {
|
|
270
|
+
statusStr = `status error: ${e.message}`;
|
|
271
|
+
}
|
|
272
|
+
const port = info ? shortenPort(info.portName) : "(unknown)";
|
|
273
|
+
const driver = info?.driverName ?? "(unknown)";
|
|
274
|
+
console.log(` #${i + 1} ${p.name}`);
|
|
275
|
+
if (info?.comment) console.log(` description: ${info.comment}`);
|
|
276
|
+
if (info?.location) console.log(` location: ${info.location}`);
|
|
277
|
+
console.log(` port: ${port}`);
|
|
278
|
+
if (info?.portName && info.portName !== port) {
|
|
279
|
+
console.log(` full port: ${info.portName}`);
|
|
280
|
+
}
|
|
281
|
+
console.log(` driver: ${driver}`);
|
|
282
|
+
if (info?.shareName && info.shareName !== p.name) {
|
|
283
|
+
console.log(` share name: ${info.shareName}`);
|
|
284
|
+
}
|
|
285
|
+
console.log(` status: ${statusStr}`);
|
|
210
286
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
console.log(
|
|
217
|
-
|
|
218
|
-
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (opts.mdns) {
|
|
290
|
+
const { discoverPdlPrinters, isBrother, modelFromTxt, matchesInstalledPort } =
|
|
291
|
+
await import("./mdns.js");
|
|
292
|
+
console.log("");
|
|
293
|
+
console.log("Browsing mDNS for _pdl-datastream._tcp (3s)...");
|
|
294
|
+
const found = (await discoverPdlPrinters()).filter(isBrother);
|
|
295
|
+
if (found.length === 0) {
|
|
296
|
+
console.log("No Brother printers responded via mDNS.");
|
|
297
|
+
return;
|
|
219
298
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
299
|
+
const installedPorts = all.map(p => p.portName);
|
|
300
|
+
const networkOnly = found.filter(m => !installedPorts.some(pn => matchesInstalledPort(m, pn)));
|
|
301
|
+
const matched = found.filter(m => installedPorts.some(pn => matchesInstalledPort(m, pn)));
|
|
302
|
+
|
|
303
|
+
console.log(`mDNS responders (${found.length}; ${matched.length} matched, ${networkOnly.length} network-only):`);
|
|
304
|
+
let n = 1;
|
|
305
|
+
for (const m of [...matched, ...networkOnly]) {
|
|
306
|
+
const isNetOnly = networkOnly.includes(m);
|
|
307
|
+
const tag = isNetOnly ? " [network only]" : " [installed]";
|
|
308
|
+
const model = modelFromTxt(m) ?? "(unknown model)";
|
|
309
|
+
console.log(` #${n++} ${m.instanceName}${tag}`);
|
|
310
|
+
console.log(` model: ${model}`);
|
|
311
|
+
console.log(` hostname: ${m.hostname.replace(/\.$/, "")}`);
|
|
312
|
+
if (m.addresses.length > 0) {
|
|
313
|
+
console.log(` address: ${m.addresses.join(", ")}`);
|
|
314
|
+
}
|
|
315
|
+
console.log(` port: ${m.port}`);
|
|
223
316
|
}
|
|
224
|
-
console.log(` status: ${statusStr}`);
|
|
225
317
|
}
|
|
226
318
|
}
|
|
227
319
|
|
package/mdns.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mDNS discovery for network-attached Brother printers.
|
|
3
|
+
*
|
|
4
|
+
* Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles
|
|
5
|
+
* full records by joining PTR -> SRV -> A -> TXT replies that arrive on the
|
|
6
|
+
* same multicast burst.
|
|
7
|
+
*/
|
|
8
|
+
export interface MdnsPrinter {
|
|
9
|
+
instanceName: string; /** service instance label, often the model */
|
|
10
|
+
hostname: string; /** e.g. BRW4CD577B59B6A.local */
|
|
11
|
+
addresses: string[]; /** IPv4 addresses */
|
|
12
|
+
port: number; /** typically 9100 */
|
|
13
|
+
txt: Record<string, string>; /** TXT record key=value pairs */
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and
|
|
17
|
+
* _printer service types and joins the records into per-instance entries
|
|
18
|
+
* keyed by service-instance name. Returns all responders (not just Brother).
|
|
19
|
+
*
|
|
20
|
+
* Sends each query 3 times spaced ~600ms apart to defeat single-packet loss
|
|
21
|
+
* — Brother printers in particular can be flaky on the first burst.
|
|
22
|
+
*/
|
|
23
|
+
export declare function discoverPdlPrinters(timeoutMs?: number): Promise<MdnsPrinter[]>;
|
|
24
|
+
export declare function isBrother(p: MdnsPrinter): boolean;
|
|
25
|
+
/** Pull a friendly model string out of TXT records. */
|
|
26
|
+
export declare function modelFromTxt(p: MdnsPrinter): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* True if `m` looks like the same physical printer as the installed Windows
|
|
29
|
+
* queue identified by `portName`. Matches by IP-address substring or by
|
|
30
|
+
* hostname (with or without trailing `.local`) appearing in the port string.
|
|
31
|
+
*/
|
|
32
|
+
export declare function matchesInstalledPort(m: MdnsPrinter, portName: string | undefined): boolean;
|
|
33
|
+
//# sourceMappingURL=mdns.d.ts.map
|
package/mdns.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdns.d.ts","sourceRoot":"","sources":["mdns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IACxB,YAAY,EAAE,MAAM,CAAC,CAAY,8CAA8C;IAC/E,QAAQ,EAAE,MAAM,CAAC,CAAgB,iCAAiC;IAClE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAa,qBAAqB;IACtD,IAAI,EAAE,MAAM,CAAC,CAAoB,qBAAqB;IACtD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAK,iCAAiC;CACrE;AAQD;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAgFlF;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAMjD;AAED,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAI/D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAU1F"}
|
package/mdns.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mDNS discovery for network-attached Brother printers.
|
|
3
|
+
*
|
|
4
|
+
* Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles
|
|
5
|
+
* full records by joining PTR -> SRV -> A -> TXT replies that arrive on the
|
|
6
|
+
* same multicast burst.
|
|
7
|
+
*/
|
|
8
|
+
import multicastDns from "multicast-dns";
|
|
9
|
+
const SERVICE_TYPES = [
|
|
10
|
+
"_pdl-datastream._tcp.local", /** raw TCP 9100 */
|
|
11
|
+
"_ipp._tcp.local", /** IPP port 631 */
|
|
12
|
+
"_printer._tcp.local", /** LPR port 515 */
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and
|
|
16
|
+
* _printer service types and joins the records into per-instance entries
|
|
17
|
+
* keyed by service-instance name. Returns all responders (not just Brother).
|
|
18
|
+
*
|
|
19
|
+
* Sends each query 3 times spaced ~600ms apart to defeat single-packet loss
|
|
20
|
+
* — Brother printers in particular can be flaky on the first burst.
|
|
21
|
+
*/
|
|
22
|
+
export async function discoverPdlPrinters(timeoutMs = 4000) {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const mdns = multicastDns();
|
|
25
|
+
const ptrs = new Map(); /** instance -> serviceType */
|
|
26
|
+
const srv = new Map();
|
|
27
|
+
const txt = new Map();
|
|
28
|
+
const aRec = new Map();
|
|
29
|
+
mdns.on("response", (response) => {
|
|
30
|
+
const records = [...(response.answers || []), ...(response.additionals || [])];
|
|
31
|
+
for (const r of records) {
|
|
32
|
+
if (r.type === "PTR" && SERVICE_TYPES.includes(r.name)) {
|
|
33
|
+
if (!ptrs.has(r.data))
|
|
34
|
+
ptrs.set(r.data, r.name);
|
|
35
|
+
}
|
|
36
|
+
else if (r.type === "SRV") {
|
|
37
|
+
srv.set(r.name, { target: r.data.target, port: r.data.port });
|
|
38
|
+
}
|
|
39
|
+
else if (r.type === "TXT") {
|
|
40
|
+
const flat = {};
|
|
41
|
+
const items = Array.isArray(r.data) ? r.data : [r.data];
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
const s = Buffer.isBuffer(item) ? item.toString("utf8") : String(item);
|
|
44
|
+
const eq = s.indexOf("=");
|
|
45
|
+
if (eq > 0)
|
|
46
|
+
flat[s.slice(0, eq)] = s.slice(eq + 1);
|
|
47
|
+
else if (s)
|
|
48
|
+
flat[s] = "";
|
|
49
|
+
}
|
|
50
|
+
txt.set(r.name, flat);
|
|
51
|
+
}
|
|
52
|
+
else if (r.type === "A") {
|
|
53
|
+
const set = aRec.get(r.name) || new Set();
|
|
54
|
+
set.add(r.data);
|
|
55
|
+
aRec.set(r.name, set);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const sendQueries = () => {
|
|
60
|
+
for (const svc of SERVICE_TYPES) {
|
|
61
|
+
mdns.query([{ name: svc, type: "PTR" }]);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
sendQueries();
|
|
65
|
+
const t1 = setTimeout(sendQueries, 600);
|
|
66
|
+
const t2 = setTimeout(sendQueries, 1500);
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
clearTimeout(t1);
|
|
69
|
+
clearTimeout(t2);
|
|
70
|
+
mdns.removeAllListeners("response");
|
|
71
|
+
mdns.destroy();
|
|
72
|
+
/** Collapse to one entry per physical device (keyed by hostname).
|
|
73
|
+
* Prefer the _pdl-datastream entry (port 9100) when available,
|
|
74
|
+
* since that's the port we'll actually print to. */
|
|
75
|
+
const byHost = new Map();
|
|
76
|
+
for (const [inst, svcType] of ptrs) {
|
|
77
|
+
const s = srv.get(inst);
|
|
78
|
+
if (!s)
|
|
79
|
+
continue;
|
|
80
|
+
const host = s.target.toLowerCase();
|
|
81
|
+
const pri = svcType.startsWith("_pdl-datastream") ? 0
|
|
82
|
+
: svcType.startsWith("_ipp") ? 1
|
|
83
|
+
: 2;
|
|
84
|
+
const cur = byHost.get(host);
|
|
85
|
+
if (!cur || pri < cur.pri)
|
|
86
|
+
byHost.set(host, { inst, svcType, pri });
|
|
87
|
+
}
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const { inst, svcType } of byHost.values()) {
|
|
90
|
+
const s = srv.get(inst);
|
|
91
|
+
const ips = [...(aRec.get(s.target) || [])];
|
|
92
|
+
const t = txt.get(inst) || {};
|
|
93
|
+
const instLabel = inst.endsWith("." + svcType)
|
|
94
|
+
? inst.slice(0, -1 - svcType.length)
|
|
95
|
+
: inst;
|
|
96
|
+
out.push({
|
|
97
|
+
instanceName: instLabel,
|
|
98
|
+
hostname: s.target,
|
|
99
|
+
addresses: ips,
|
|
100
|
+
port: s.port,
|
|
101
|
+
txt: t,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
resolve(out);
|
|
105
|
+
}, timeoutMs);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function isBrother(p) {
|
|
109
|
+
if (p.txt.usb_MFG && /brother/i.test(p.txt.usb_MFG))
|
|
110
|
+
return true;
|
|
111
|
+
if (p.txt.ty && /brother/i.test(p.txt.ty))
|
|
112
|
+
return true;
|
|
113
|
+
if (p.txt.product && /brother/i.test(p.txt.product))
|
|
114
|
+
return true;
|
|
115
|
+
if (/^BRW/i.test(p.instanceName) || /^BRW/i.test(p.hostname))
|
|
116
|
+
return true;
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/** Pull a friendly model string out of TXT records. */
|
|
120
|
+
export function modelFromTxt(p) {
|
|
121
|
+
if (p.txt.ty)
|
|
122
|
+
return p.txt.ty;
|
|
123
|
+
if (p.txt.product)
|
|
124
|
+
return p.txt.product.replace(/^\((.*)\)$/, "$1");
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* True if `m` looks like the same physical printer as the installed Windows
|
|
129
|
+
* queue identified by `portName`. Matches by IP-address substring or by
|
|
130
|
+
* hostname (with or without trailing `.local`) appearing in the port string.
|
|
131
|
+
*/
|
|
132
|
+
export function matchesInstalledPort(m, portName) {
|
|
133
|
+
if (!portName)
|
|
134
|
+
return false;
|
|
135
|
+
const p = portName.toLowerCase();
|
|
136
|
+
if (m.addresses.some(ip => p.includes(ip.toLowerCase())))
|
|
137
|
+
return true;
|
|
138
|
+
const host = m.hostname.toLowerCase().replace(/\.$/, "");
|
|
139
|
+
const bare = host.replace(/\.local$/, "");
|
|
140
|
+
if (host && p.includes(host))
|
|
141
|
+
return true;
|
|
142
|
+
if (bare && p.includes(bare))
|
|
143
|
+
return true;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=mdns.js.map
|
package/mdns.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdns.js","sourceRoot":"","sources":["mdns.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * mDNS discovery for network-attached Brother printers.\n *\n * Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles\n * full records by joining PTR -> SRV -> A -> TXT replies that arrive on the\n * same multicast burst.\n */\n\nimport multicastDns from \"multicast-dns\";\n\nexport interface MdnsPrinter {\n instanceName: string; /** service instance label, often the model */\n hostname: string; /** e.g. BRW4CD577B59B6A.local */\n addresses: string[]; /** IPv4 addresses */\n port: number; /** typically 9100 */\n txt: Record<string, string>; /** TXT record key=value pairs */\n}\n\nconst SERVICE_TYPES = [\n \"_pdl-datastream._tcp.local\", /** raw TCP 9100 */\n \"_ipp._tcp.local\", /** IPP port 631 */\n \"_printer._tcp.local\", /** LPR port 515 */\n];\n\n/**\n * Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and\n * _printer service types and joins the records into per-instance entries\n * keyed by service-instance name. Returns all responders (not just Brother).\n *\n * Sends each query 3 times spaced ~600ms apart to defeat single-packet loss\n * — Brother printers in particular can be flaky on the first burst.\n */\nexport async function discoverPdlPrinters(timeoutMs = 4000): Promise<MdnsPrinter[]> {\n return new Promise<MdnsPrinter[]>((resolve) => {\n const mdns = multicastDns();\n const ptrs = new Map<string, string>(); /** instance -> serviceType */\n const srv = new Map<string, { target: string; port: number }>();\n const txt = new Map<string, Record<string, string>>();\n const aRec = new Map<string, Set<string>>();\n\n mdns.on(\"response\", (response: any) => {\n const records = [...(response.answers || []), ...(response.additionals || [])];\n for (const r of records) {\n if (r.type === \"PTR\" && SERVICE_TYPES.includes(r.name)) {\n if (!ptrs.has(r.data)) ptrs.set(r.data, r.name);\n } else if (r.type === \"SRV\") {\n srv.set(r.name, { target: r.data.target, port: r.data.port });\n } else if (r.type === \"TXT\") {\n const flat: Record<string, string> = {};\n const items: any[] = Array.isArray(r.data) ? r.data : [r.data];\n for (const item of items) {\n const s = Buffer.isBuffer(item) ? item.toString(\"utf8\") : String(item);\n const eq = s.indexOf(\"=\");\n if (eq > 0) flat[s.slice(0, eq)] = s.slice(eq + 1);\n else if (s) flat[s] = \"\";\n }\n txt.set(r.name, flat);\n } else if (r.type === \"A\") {\n const set = aRec.get(r.name) || new Set<string>();\n set.add(r.data);\n aRec.set(r.name, set);\n }\n }\n });\n\n const sendQueries = () => {\n for (const svc of SERVICE_TYPES) {\n mdns.query([{ name: svc, type: \"PTR\" }]);\n }\n };\n sendQueries();\n const t1 = setTimeout(sendQueries, 600);\n const t2 = setTimeout(sendQueries, 1500);\n\n setTimeout(() => {\n clearTimeout(t1);\n clearTimeout(t2);\n mdns.removeAllListeners(\"response\");\n mdns.destroy();\n /** Collapse to one entry per physical device (keyed by hostname).\n * Prefer the _pdl-datastream entry (port 9100) when available,\n * since that's the port we'll actually print to. */\n const byHost = new Map<string, { inst: string; svcType: string; pri: number }>();\n for (const [inst, svcType] of ptrs) {\n const s = srv.get(inst);\n if (!s) continue;\n const host = s.target.toLowerCase();\n const pri = svcType.startsWith(\"_pdl-datastream\") ? 0\n : svcType.startsWith(\"_ipp\") ? 1\n : 2;\n const cur = byHost.get(host);\n if (!cur || pri < cur.pri) byHost.set(host, { inst, svcType, pri });\n }\n const out: MdnsPrinter[] = [];\n for (const { inst, svcType } of byHost.values()) {\n const s = srv.get(inst)!;\n const ips = [...(aRec.get(s.target) || [])];\n const t = txt.get(inst) || {};\n const instLabel = inst.endsWith(\".\" + svcType)\n ? inst.slice(0, -1 - svcType.length)\n : inst;\n out.push({\n instanceName: instLabel,\n hostname: s.target,\n addresses: ips,\n port: s.port,\n txt: t,\n });\n }\n resolve(out);\n }, timeoutMs);\n });\n}\n\nexport function isBrother(p: MdnsPrinter): boolean {\n if (p.txt.usb_MFG && /brother/i.test(p.txt.usb_MFG)) return true;\n if (p.txt.ty && /brother/i.test(p.txt.ty)) return true;\n if (p.txt.product && /brother/i.test(p.txt.product)) return true;\n if (/^BRW/i.test(p.instanceName) || /^BRW/i.test(p.hostname)) return true;\n return false;\n}\n\n/** Pull a friendly model string out of TXT records. */\nexport function modelFromTxt(p: MdnsPrinter): string | undefined {\n if (p.txt.ty) return p.txt.ty;\n if (p.txt.product) return p.txt.product.replace(/^\\((.*)\\)$/, \"$1\");\n return undefined;\n}\n\n/**\n * True if `m` looks like the same physical printer as the installed Windows\n * queue identified by `portName`. Matches by IP-address substring or by\n * hostname (with or without trailing `.local`) appearing in the port string.\n */\nexport function matchesInstalledPort(m: MdnsPrinter, portName: string | undefined): boolean {\n if (!portName) return false;\n const p = portName.toLowerCase();\n if (m.addresses.some(ip => p.includes(ip.toLowerCase()))) return true;\n\n const host = m.hostname.toLowerCase().replace(/\\.$/, \"\");\n const bare = host.replace(/\\.local$/, \"\");\n if (host && p.includes(host)) return true;\n if (bare && p.includes(bare)) return true;\n return false;\n}\n"]}
|
package/mdns.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mDNS discovery for network-attached Brother printers.
|
|
3
|
+
*
|
|
4
|
+
* Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles
|
|
5
|
+
* full records by joining PTR -> SRV -> A -> TXT replies that arrive on the
|
|
6
|
+
* same multicast burst.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import multicastDns from "multicast-dns";
|
|
10
|
+
|
|
11
|
+
export interface MdnsPrinter {
|
|
12
|
+
instanceName: string; /** service instance label, often the model */
|
|
13
|
+
hostname: string; /** e.g. BRW4CD577B59B6A.local */
|
|
14
|
+
addresses: string[]; /** IPv4 addresses */
|
|
15
|
+
port: number; /** typically 9100 */
|
|
16
|
+
txt: Record<string, string>; /** TXT record key=value pairs */
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const SERVICE_TYPES = [
|
|
20
|
+
"_pdl-datastream._tcp.local", /** raw TCP 9100 */
|
|
21
|
+
"_ipp._tcp.local", /** IPP port 631 */
|
|
22
|
+
"_printer._tcp.local", /** LPR port 515 */
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and
|
|
27
|
+
* _printer service types and joins the records into per-instance entries
|
|
28
|
+
* keyed by service-instance name. Returns all responders (not just Brother).
|
|
29
|
+
*
|
|
30
|
+
* Sends each query 3 times spaced ~600ms apart to defeat single-packet loss
|
|
31
|
+
* — Brother printers in particular can be flaky on the first burst.
|
|
32
|
+
*/
|
|
33
|
+
export async function discoverPdlPrinters(timeoutMs = 4000): Promise<MdnsPrinter[]> {
|
|
34
|
+
return new Promise<MdnsPrinter[]>((resolve) => {
|
|
35
|
+
const mdns = multicastDns();
|
|
36
|
+
const ptrs = new Map<string, string>(); /** instance -> serviceType */
|
|
37
|
+
const srv = new Map<string, { target: string; port: number }>();
|
|
38
|
+
const txt = new Map<string, Record<string, string>>();
|
|
39
|
+
const aRec = new Map<string, Set<string>>();
|
|
40
|
+
|
|
41
|
+
mdns.on("response", (response: any) => {
|
|
42
|
+
const records = [...(response.answers || []), ...(response.additionals || [])];
|
|
43
|
+
for (const r of records) {
|
|
44
|
+
if (r.type === "PTR" && SERVICE_TYPES.includes(r.name)) {
|
|
45
|
+
if (!ptrs.has(r.data)) ptrs.set(r.data, r.name);
|
|
46
|
+
} else if (r.type === "SRV") {
|
|
47
|
+
srv.set(r.name, { target: r.data.target, port: r.data.port });
|
|
48
|
+
} else if (r.type === "TXT") {
|
|
49
|
+
const flat: Record<string, string> = {};
|
|
50
|
+
const items: any[] = Array.isArray(r.data) ? r.data : [r.data];
|
|
51
|
+
for (const item of items) {
|
|
52
|
+
const s = Buffer.isBuffer(item) ? item.toString("utf8") : String(item);
|
|
53
|
+
const eq = s.indexOf("=");
|
|
54
|
+
if (eq > 0) flat[s.slice(0, eq)] = s.slice(eq + 1);
|
|
55
|
+
else if (s) flat[s] = "";
|
|
56
|
+
}
|
|
57
|
+
txt.set(r.name, flat);
|
|
58
|
+
} else if (r.type === "A") {
|
|
59
|
+
const set = aRec.get(r.name) || new Set<string>();
|
|
60
|
+
set.add(r.data);
|
|
61
|
+
aRec.set(r.name, set);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const sendQueries = () => {
|
|
67
|
+
for (const svc of SERVICE_TYPES) {
|
|
68
|
+
mdns.query([{ name: svc, type: "PTR" }]);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
sendQueries();
|
|
72
|
+
const t1 = setTimeout(sendQueries, 600);
|
|
73
|
+
const t2 = setTimeout(sendQueries, 1500);
|
|
74
|
+
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
clearTimeout(t1);
|
|
77
|
+
clearTimeout(t2);
|
|
78
|
+
mdns.removeAllListeners("response");
|
|
79
|
+
mdns.destroy();
|
|
80
|
+
/** Collapse to one entry per physical device (keyed by hostname).
|
|
81
|
+
* Prefer the _pdl-datastream entry (port 9100) when available,
|
|
82
|
+
* since that's the port we'll actually print to. */
|
|
83
|
+
const byHost = new Map<string, { inst: string; svcType: string; pri: number }>();
|
|
84
|
+
for (const [inst, svcType] of ptrs) {
|
|
85
|
+
const s = srv.get(inst);
|
|
86
|
+
if (!s) continue;
|
|
87
|
+
const host = s.target.toLowerCase();
|
|
88
|
+
const pri = svcType.startsWith("_pdl-datastream") ? 0
|
|
89
|
+
: svcType.startsWith("_ipp") ? 1
|
|
90
|
+
: 2;
|
|
91
|
+
const cur = byHost.get(host);
|
|
92
|
+
if (!cur || pri < cur.pri) byHost.set(host, { inst, svcType, pri });
|
|
93
|
+
}
|
|
94
|
+
const out: MdnsPrinter[] = [];
|
|
95
|
+
for (const { inst, svcType } of byHost.values()) {
|
|
96
|
+
const s = srv.get(inst)!;
|
|
97
|
+
const ips = [...(aRec.get(s.target) || [])];
|
|
98
|
+
const t = txt.get(inst) || {};
|
|
99
|
+
const instLabel = inst.endsWith("." + svcType)
|
|
100
|
+
? inst.slice(0, -1 - svcType.length)
|
|
101
|
+
: inst;
|
|
102
|
+
out.push({
|
|
103
|
+
instanceName: instLabel,
|
|
104
|
+
hostname: s.target,
|
|
105
|
+
addresses: ips,
|
|
106
|
+
port: s.port,
|
|
107
|
+
txt: t,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
resolve(out);
|
|
111
|
+
}, timeoutMs);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function isBrother(p: MdnsPrinter): boolean {
|
|
116
|
+
if (p.txt.usb_MFG && /brother/i.test(p.txt.usb_MFG)) return true;
|
|
117
|
+
if (p.txt.ty && /brother/i.test(p.txt.ty)) return true;
|
|
118
|
+
if (p.txt.product && /brother/i.test(p.txt.product)) return true;
|
|
119
|
+
if (/^BRW/i.test(p.instanceName) || /^BRW/i.test(p.hostname)) return true;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Pull a friendly model string out of TXT records. */
|
|
124
|
+
export function modelFromTxt(p: MdnsPrinter): string | undefined {
|
|
125
|
+
if (p.txt.ty) return p.txt.ty;
|
|
126
|
+
if (p.txt.product) return p.txt.product.replace(/^\((.*)\)$/, "$1");
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* True if `m` looks like the same physical printer as the installed Windows
|
|
132
|
+
* queue identified by `portName`. Matches by IP-address substring or by
|
|
133
|
+
* hostname (with or without trailing `.local`) appearing in the port string.
|
|
134
|
+
*/
|
|
135
|
+
export function matchesInstalledPort(m: MdnsPrinter, portName: string | undefined): boolean {
|
|
136
|
+
if (!portName) return false;
|
|
137
|
+
const p = portName.toLowerCase();
|
|
138
|
+
if (m.addresses.some(ip => p.includes(ip.toLowerCase()))) return true;
|
|
139
|
+
|
|
140
|
+
const host = m.hostname.toLowerCase().replace(/\.$/, "");
|
|
141
|
+
const bare = host.replace(/\.local$/, "");
|
|
142
|
+
if (host && p.includes(host)) return true;
|
|
143
|
+
if (bare && p.includes(bare)) return true;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/brother-label",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.8",
|
|
4
4
|
"description": "API and CLI for printing labels on Brother P-touch printers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -45,20 +45,24 @@
|
|
|
45
45
|
"url": "https://github.com/BobFrankston/brother-label.git"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@bobfrankston/label-core": "^0.1.11"
|
|
48
|
+
"@bobfrankston/label-core": "^0.1.11",
|
|
49
|
+
"multicast-dns": "^7.2.5"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
52
|
+
"@types/multicast-dns": "^7.2.4",
|
|
51
53
|
"@types/node": "^25.2.1"
|
|
52
54
|
},
|
|
53
55
|
"publishConfig": {
|
|
54
56
|
"access": "public"
|
|
55
57
|
},
|
|
56
58
|
".dependencies": {
|
|
57
|
-
"@bobfrankston/label-core": "file:../../../utils/label-core"
|
|
59
|
+
"@bobfrankston/label-core": "file:../../../utils/label-core",
|
|
60
|
+
"multicast-dns": "^7.2.5"
|
|
58
61
|
},
|
|
59
62
|
".transformedSnapshot": {
|
|
60
63
|
"dependencies": {
|
|
61
|
-
"@bobfrankston/label-core": "^0.1.11"
|
|
64
|
+
"@bobfrankston/label-core": "^0.1.11",
|
|
65
|
+
"multicast-dns": "^7.2.5"
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
}
|