@willwade/aac-processors 0.1.19 → 0.1.21
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/dist/browser/core/baseProcessor.js +4 -0
- package/dist/browser/processors/applePanelsProcessor.js +24 -31
- package/dist/browser/processors/astericsGridProcessor.js +10 -3
- package/dist/browser/processors/dotProcessor.js +5 -2
- package/dist/browser/processors/gridset/colorUtils.js +354 -0
- package/dist/browser/processors/gridset/helpers.js +49 -45
- package/dist/browser/processors/gridset/index.js +61 -0
- package/dist/browser/processors/gridset/styleHelpers.js +205 -0
- package/dist/browser/processors/gridset/symbolExtractor.js +331 -0
- package/dist/browser/processors/gridset/symbolSearch.js +248 -0
- package/dist/browser/processors/gridset/symbols.js +35 -68
- package/dist/browser/processors/gridsetProcessor.js +32 -41
- package/dist/browser/processors/obfProcessor.js +53 -45
- package/dist/browser/processors/opmlProcessor.js +5 -2
- package/dist/browser/processors/snap/helpers.js +49 -45
- package/dist/browser/processors/snapProcessor.js +67 -31
- package/dist/browser/processors/touchchatProcessor.js +54 -45
- package/dist/browser/utilities/analytics/reference/index.js +27 -19
- package/dist/browser/utils/io.js +67 -14
- package/dist/browser/utils/sqlite.js +6 -8
- package/dist/browser/utils/zip.js +45 -43
- package/dist/browser/validation/baseValidator.js +5 -0
- package/dist/browser/validation/gridsetValidator.js +12 -20
- package/dist/browser/validation/obfValidator.js +5 -4
- package/dist/browser/validation/snapValidator.js +9 -5
- package/dist/browser/validation/touchChatValidator.js +21 -11
- package/dist/cli/index.js +10 -15
- package/dist/core/baseProcessor.d.ts +7 -7
- package/dist/core/baseProcessor.js +4 -0
- package/dist/processors/applePanelsProcessor.js +29 -36
- package/dist/processors/astericsGridProcessor.js +20 -13
- package/dist/processors/dotProcessor.js +10 -7
- package/dist/processors/excelProcessor.js +9 -12
- package/dist/processors/gridset/helpers.d.ts +9 -11
- package/dist/processors/gridset/helpers.js +49 -71
- package/dist/processors/gridset/imageDebug.d.ts +3 -5
- package/dist/processors/gridset/imageDebug.js +4 -4
- package/dist/processors/gridset/password.d.ts +1 -1
- package/dist/processors/gridset/symbolExtractor.d.ts +5 -3
- package/dist/processors/gridset/symbolExtractor.js +15 -38
- package/dist/processors/gridset/symbolSearch.d.ts +3 -2
- package/dist/processors/gridset/symbolSearch.js +12 -34
- package/dist/processors/gridset/symbols.d.ts +8 -6
- package/dist/processors/gridset/symbols.js +34 -67
- package/dist/processors/gridset/wordlistHelpers.d.ts +4 -6
- package/dist/processors/gridset/wordlistHelpers.js +15 -74
- package/dist/processors/gridsetProcessor.js +36 -68
- package/dist/processors/obfProcessor.js +58 -73
- package/dist/processors/obfsetProcessor.js +2 -2
- package/dist/processors/opmlProcessor.js +10 -7
- package/dist/processors/snap/helpers.d.ts +8 -8
- package/dist/processors/snap/helpers.js +50 -72
- package/dist/processors/snapProcessor.js +66 -30
- package/dist/processors/touchchatProcessor.js +54 -45
- package/dist/utilities/analytics/index.d.ts +3 -2
- package/dist/utilities/analytics/index.js +8 -10
- package/dist/utilities/analytics/reference/index.d.ts +5 -3
- package/dist/utilities/analytics/reference/index.js +26 -18
- package/dist/utilities/symbolTools.d.ts +4 -2
- package/dist/utilities/symbolTools.js +16 -15
- package/dist/utils/io.d.ts +24 -6
- package/dist/utils/io.js +64 -14
- package/dist/utils/sqlite.d.ts +2 -0
- package/dist/utils/sqlite.js +6 -8
- package/dist/utils/zip.d.ts +7 -3
- package/dist/utils/zip.js +45 -43
- package/dist/validation/applePanelsValidator.d.ts +2 -1
- package/dist/validation/applePanelsValidator.js +9 -11
- package/dist/validation/astericsValidator.d.ts +2 -1
- package/dist/validation/astericsValidator.js +5 -4
- package/dist/validation/baseValidator.d.ts +2 -2
- package/dist/validation/baseValidator.js +5 -0
- package/dist/validation/dotValidator.d.ts +2 -1
- package/dist/validation/dotValidator.js +5 -4
- package/dist/validation/excelValidator.d.ts +2 -1
- package/dist/validation/excelValidator.js +5 -4
- package/dist/validation/gridsetValidator.d.ts +2 -1
- package/dist/validation/gridsetValidator.js +11 -22
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +5 -4
- package/dist/validation/obfValidator.d.ts +2 -1
- package/dist/validation/obfValidator.js +5 -4
- package/dist/validation/obfsetValidator.d.ts +2 -1
- package/dist/validation/obfsetValidator.js +5 -4
- package/dist/validation/opmlValidator.d.ts +2 -1
- package/dist/validation/opmlValidator.js +5 -4
- package/dist/validation/snapValidator.d.ts +2 -1
- package/dist/validation/snapValidator.js +9 -5
- package/dist/validation/touchChatValidator.d.ts +4 -6
- package/dist/validation/touchChatValidator.js +21 -11
- package/dist/validation/validationTypes.d.ts +8 -1
- package/package.json +1 -1
- package/dist/core/fileProcessor.d.ts +0 -7
- package/dist/core/fileProcessor.js +0 -52
|
@@ -1,27 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
3
|
exports.ObfProcessor = void 0;
|
|
27
4
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
@@ -146,13 +123,9 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
146
123
|
return 'image/png';
|
|
147
124
|
}
|
|
148
125
|
}
|
|
149
|
-
async processBoard(boardData, _boardPath) {
|
|
126
|
+
async processBoard(boardData, _boardPath, isZipEntry) {
|
|
150
127
|
const sourceButtons = boardData.buttons || [];
|
|
151
128
|
// Calculate page ID first (used to make button IDs unique)
|
|
152
|
-
const isZipEntry = _boardPath &&
|
|
153
|
-
_boardPath.endsWith('.obf') &&
|
|
154
|
-
!_boardPath.includes('/') &&
|
|
155
|
-
!_boardPath.includes('\\');
|
|
156
129
|
const pageId = isZipEntry
|
|
157
130
|
? _boardPath // Zip entry - use filename to match navigation paths
|
|
158
131
|
: boardData?.id
|
|
@@ -313,10 +286,11 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
313
286
|
return texts;
|
|
314
287
|
}
|
|
315
288
|
async loadIntoTree(filePathOrBuffer) {
|
|
289
|
+
const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
|
|
316
290
|
// Detailed logging for debugging input
|
|
317
291
|
const bufferLength = typeof filePathOrBuffer === 'string'
|
|
318
292
|
? null
|
|
319
|
-
:
|
|
293
|
+
: readBinaryFromInput(filePathOrBuffer).byteLength;
|
|
320
294
|
console.log('[OBF] loadIntoTree called with:', {
|
|
321
295
|
type: typeof filePathOrBuffer,
|
|
322
296
|
isBuffer: typeof Buffer !== 'undefined' && Buffer.isBuffer(filePathOrBuffer),
|
|
@@ -328,7 +302,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
328
302
|
// Helper: try to parse JSON OBF
|
|
329
303
|
function tryParseObfJson(data) {
|
|
330
304
|
try {
|
|
331
|
-
const str = typeof data === 'string' ? data :
|
|
305
|
+
const str = typeof data === 'string' ? data : readTextFromInput(data);
|
|
332
306
|
// Check for empty or whitespace-only content
|
|
333
307
|
if (!str.trim()) {
|
|
334
308
|
return null;
|
|
@@ -350,11 +324,11 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
350
324
|
// If input is a string path and ends with .obf, treat as JSON
|
|
351
325
|
if (typeof filePathOrBuffer === 'string' && filePathOrBuffer.toLowerCase().endsWith('.obf')) {
|
|
352
326
|
try {
|
|
353
|
-
const content =
|
|
327
|
+
const content = readTextFromInput(filePathOrBuffer);
|
|
354
328
|
const boardData = tryParseObfJson(content);
|
|
355
329
|
if (boardData) {
|
|
356
330
|
console.log('[OBF] Detected .obf file, parsed as JSON');
|
|
357
|
-
const page = await this.processBoard(boardData, filePathOrBuffer);
|
|
331
|
+
const page = await this.processBoard(boardData, filePathOrBuffer, false);
|
|
358
332
|
tree.addPage(page);
|
|
359
333
|
// Set metadata from root board
|
|
360
334
|
tree.metadata.format = 'obf';
|
|
@@ -384,7 +358,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
384
358
|
const lowered = input.toLowerCase();
|
|
385
359
|
return lowered.endsWith('.zip') || lowered.endsWith('.obz');
|
|
386
360
|
}
|
|
387
|
-
const bytes =
|
|
361
|
+
const bytes = readBinaryFromInput(input);
|
|
388
362
|
return bytes.length >= 2 && bytes[0] === 0x50 && bytes[1] === 0x4b;
|
|
389
363
|
}
|
|
390
364
|
// Check if input is a buffer or string that parses as OBF JSON; throw if neither JSON nor ZIP
|
|
@@ -393,7 +367,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
393
367
|
if (!asJson)
|
|
394
368
|
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
395
369
|
console.log('[OBF] Detected buffer/string as OBF JSON');
|
|
396
|
-
const page = await this.processBoard(asJson, '[bufferOrString]');
|
|
370
|
+
const page = await this.processBoard(asJson, '[bufferOrString]', false);
|
|
397
371
|
tree.addPage(page);
|
|
398
372
|
// Set metadata from root board
|
|
399
373
|
tree.metadata.format = 'obf';
|
|
@@ -410,10 +384,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
410
384
|
return tree;
|
|
411
385
|
}
|
|
412
386
|
try {
|
|
413
|
-
|
|
414
|
-
? await this.options.zipAdapter(filePathOrBuffer)
|
|
415
|
-
: await (0, zip_1.openZipFromInput)(filePathOrBuffer);
|
|
416
|
-
this.zipFile = zipResult.zip;
|
|
387
|
+
this.zipFile = await this.options.zipAdapter(filePathOrBuffer);
|
|
417
388
|
}
|
|
418
389
|
catch (err) {
|
|
419
390
|
console.error('[OBF] Error loading ZIP:', err);
|
|
@@ -422,17 +393,42 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
422
393
|
// Store the ZIP file reference for image extraction
|
|
423
394
|
this.imageCache.clear(); // Clear cache for new file
|
|
424
395
|
console.log('[OBF] Detected zip archive, extracting .obf files');
|
|
425
|
-
//
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
396
|
+
// List manifest and OBF files
|
|
397
|
+
const filesInZip = this.zipFile.listFiles();
|
|
398
|
+
const manifestFile = filesInZip.filter((name) => name.toLowerCase() === 'manifest.json');
|
|
399
|
+
let obfEntries = filesInZip.filter((name) => name.toLowerCase().endsWith('.obf'));
|
|
400
|
+
// Attempt to read manifest
|
|
401
|
+
if (manifestFile && manifestFile.length === 1) {
|
|
402
|
+
try {
|
|
403
|
+
const content = await this.zipFile.readFile(manifestFile[0]);
|
|
404
|
+
const data = (0, io_1.decodeText)(content);
|
|
405
|
+
const str = typeof data === 'string' ? data : readTextFromInput(data);
|
|
406
|
+
if (!str.trim())
|
|
407
|
+
throw new Error('Manifest object missing');
|
|
408
|
+
const manifestObject = JSON.parse(str);
|
|
409
|
+
if (!manifestObject)
|
|
410
|
+
throw new Error('Manifest object is empty');
|
|
411
|
+
// Replace OBF file list
|
|
412
|
+
if (manifestObject.paths && manifestObject.paths.boards) {
|
|
413
|
+
obfEntries = Object.values(manifestObject.paths.boards);
|
|
414
|
+
}
|
|
415
|
+
// Move root board to top of list
|
|
416
|
+
if (manifestObject.root) {
|
|
417
|
+
obfEntries = obfEntries.filter((item) => item !== manifestObject.root);
|
|
418
|
+
obfEntries.unshift(manifestObject.root);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch (err) {
|
|
422
|
+
console.warn('[OBF] Error processing mainfest', err);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
429
425
|
// Process each .obf entry
|
|
430
426
|
for (const entryName of obfEntries) {
|
|
431
427
|
try {
|
|
432
428
|
const content = await this.zipFile.readFile(entryName);
|
|
433
429
|
const boardData = tryParseObfJson((0, io_1.decodeText)(content));
|
|
434
430
|
if (boardData) {
|
|
435
|
-
const page = await this.processBoard(boardData, entryName);
|
|
431
|
+
const page = await this.processBoard(boardData, entryName, true);
|
|
436
432
|
tree.addPage(page);
|
|
437
433
|
// Set metadata if not already set (use first board as reference)
|
|
438
434
|
if (!tree.metadata.format) {
|
|
@@ -544,6 +540,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
544
540
|
};
|
|
545
541
|
}
|
|
546
542
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
543
|
+
const { readBinaryFromInput } = this.options.fileAdapter;
|
|
547
544
|
// Load the tree, apply translations, and save to new file
|
|
548
545
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
549
546
|
// Apply translations to all text content
|
|
@@ -573,9 +570,10 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
573
570
|
});
|
|
574
571
|
// Save the translated tree and return its content
|
|
575
572
|
await this.saveFromTree(tree, outputPath);
|
|
576
|
-
return
|
|
573
|
+
return readBinaryFromInput(outputPath);
|
|
577
574
|
}
|
|
578
575
|
async saveFromTree(tree, outputPath) {
|
|
576
|
+
const { writeTextToPath, writeBinaryToPath } = this.options.fileAdapter;
|
|
579
577
|
if (outputPath.endsWith('.obf')) {
|
|
580
578
|
// Save as single OBF JSON file
|
|
581
579
|
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
@@ -583,35 +581,21 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
583
581
|
throw new Error('No pages to save');
|
|
584
582
|
}
|
|
585
583
|
const obfBoard = this.createObfBoardFromPage(rootPage, 'Exported Board', tree.metadata);
|
|
586
|
-
|
|
584
|
+
writeTextToPath(outputPath, JSON.stringify(obfBoard, null, 2));
|
|
587
585
|
}
|
|
588
586
|
else {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
const module = await Promise.resolve().then(() => __importStar(require('jszip')));
|
|
604
|
-
const JSZip = module.default || module;
|
|
605
|
-
const zip = new JSZip();
|
|
606
|
-
Object.values(tree.pages).forEach((page) => {
|
|
607
|
-
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
|
|
608
|
-
const obfContent = JSON.stringify(obfBoard, null, 2);
|
|
609
|
-
zip.file(`${page.id}.obf`, obfContent);
|
|
610
|
-
});
|
|
611
|
-
const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
|
|
612
|
-
const { writeBinaryToPath } = await Promise.resolve().then(() => __importStar(require('../utils/io')));
|
|
613
|
-
writeBinaryToPath(outputPath, zipBuffer);
|
|
614
|
-
}
|
|
587
|
+
const files = Object.values(tree.pages).map((page) => {
|
|
588
|
+
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
|
|
589
|
+
const obfContent = JSON.stringify(obfBoard, null, 2);
|
|
590
|
+
const name = page.id.endsWith('.obf') ? page.id : `${page.id}.obf`;
|
|
591
|
+
return {
|
|
592
|
+
name,
|
|
593
|
+
data: new TextEncoder().encode(obfContent),
|
|
594
|
+
};
|
|
595
|
+
});
|
|
596
|
+
const zip = await (0, zip_1.getZipAdapter)(undefined, this.options.fileAdapter);
|
|
597
|
+
const zipData = await zip.writeFiles(files);
|
|
598
|
+
writeBinaryToPath(outputPath, zipData);
|
|
615
599
|
}
|
|
616
600
|
}
|
|
617
601
|
/**
|
|
@@ -635,7 +619,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
635
619
|
*/
|
|
636
620
|
async validate(filePath) {
|
|
637
621
|
const ObfValidator = this.getObfValidator();
|
|
638
|
-
return ObfValidator.validateFile(filePath);
|
|
622
|
+
return ObfValidator.validateFile(filePath, this.options.fileAdapter);
|
|
639
623
|
}
|
|
640
624
|
/**
|
|
641
625
|
* Extract symbol information from an OBF/OBZ file for LLM-based translation.
|
|
@@ -677,6 +661,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
677
661
|
* @returns Buffer of the translated OBF/OBZ file
|
|
678
662
|
*/
|
|
679
663
|
async processLLMTranslations(filePathOrBuffer, llmTranslations, outputPath, options) {
|
|
664
|
+
const { readBinaryFromInput } = this.options.fileAdapter;
|
|
680
665
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
681
666
|
// Validate translations using shared utility
|
|
682
667
|
const buttonIds = Object.values(tree.pages).flatMap((page) => page.buttons.map((b) => b.id));
|
|
@@ -715,7 +700,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
715
700
|
});
|
|
716
701
|
// Save and return
|
|
717
702
|
await this.saveFromTree(tree, outputPath);
|
|
718
|
-
return
|
|
703
|
+
return readBinaryFromInput(outputPath);
|
|
719
704
|
}
|
|
720
705
|
getObfValidator() {
|
|
721
706
|
try {
|
|
@@ -8,7 +8,6 @@ exports.ObfsetProcessor = void 0;
|
|
|
8
8
|
const treeStructure_1 = require("../core/treeStructure");
|
|
9
9
|
const treeStructure_2 = require("../core/treeStructure");
|
|
10
10
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
11
|
-
const io_1 = require("../utils/io");
|
|
12
11
|
class ObfsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
13
12
|
constructor(options = {}) {
|
|
14
13
|
super(options);
|
|
@@ -33,10 +32,11 @@ class ObfsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
33
32
|
* Load an .obfset file (JSON array of boards)
|
|
34
33
|
*/
|
|
35
34
|
async loadIntoTree(filePathOrBuffer) {
|
|
35
|
+
const { readTextFromInput } = this.options.fileAdapter;
|
|
36
36
|
await Promise.resolve();
|
|
37
37
|
const tree = new treeStructure_1.AACTree();
|
|
38
38
|
tree.metadata.format = 'obfset';
|
|
39
|
-
const content =
|
|
39
|
+
const content = readTextFromInput(filePathOrBuffer);
|
|
40
40
|
const boards = JSON.parse(content);
|
|
41
41
|
// Track board ID mappings
|
|
42
42
|
const boardMap = new Map();
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.OpmlProcessor = void 0;
|
|
4
4
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
5
5
|
const treeStructure_1 = require("../core/treeStructure");
|
|
6
|
-
// Removed unused import: FileProcessor
|
|
7
6
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
8
7
|
const validationTypes_1 = require("../validation/validationTypes");
|
|
9
8
|
const io_1 = require("../utils/io");
|
|
@@ -53,8 +52,9 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
53
52
|
return { page, childPages };
|
|
54
53
|
}
|
|
55
54
|
async extractTexts(filePathOrBuffer) {
|
|
55
|
+
const { readTextFromInput } = this.options.fileAdapter;
|
|
56
56
|
await Promise.resolve();
|
|
57
|
-
const content =
|
|
57
|
+
const content = readTextFromInput(filePathOrBuffer);
|
|
58
58
|
const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false });
|
|
59
59
|
const data = parser.parse(content);
|
|
60
60
|
const texts = [];
|
|
@@ -85,10 +85,11 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
85
85
|
return texts;
|
|
86
86
|
}
|
|
87
87
|
async loadIntoTree(filePathOrBuffer) {
|
|
88
|
+
const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
|
|
88
89
|
await Promise.resolve();
|
|
89
90
|
const filename = typeof filePathOrBuffer === 'string' ? (0, io_1.getBasename)(filePathOrBuffer) : 'upload.opml';
|
|
90
|
-
const buffer =
|
|
91
|
-
const content =
|
|
91
|
+
const buffer = readBinaryFromInput(filePathOrBuffer);
|
|
92
|
+
const content = readTextFromInput(buffer);
|
|
92
93
|
try {
|
|
93
94
|
if (!content || !content.trim()) {
|
|
94
95
|
const validationResult = (0, validationTypes_1.buildValidationResultFromMessage)({
|
|
@@ -171,8 +172,9 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
175
|
+
const { writeBinaryToPath, readTextFromInput } = this.options.fileAdapter;
|
|
174
176
|
await Promise.resolve();
|
|
175
|
-
const content =
|
|
177
|
+
const content = readTextFromInput(filePathOrBuffer);
|
|
176
178
|
let translatedContent = content;
|
|
177
179
|
// Apply translations to text attributes in OPML outline elements
|
|
178
180
|
translations.forEach((translation, originalText) => {
|
|
@@ -183,10 +185,11 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
183
185
|
}
|
|
184
186
|
});
|
|
185
187
|
const resultBuffer = (0, io_1.encodeText)(translatedContent);
|
|
186
|
-
|
|
188
|
+
writeBinaryToPath(outputPath, resultBuffer);
|
|
187
189
|
return resultBuffer;
|
|
188
190
|
}
|
|
189
191
|
async saveFromTree(tree, outputPath) {
|
|
192
|
+
const { writeTextToPath } = this.options.fileAdapter;
|
|
190
193
|
await Promise.resolve();
|
|
191
194
|
// Helper to recursively build outline nodes with cycle detection
|
|
192
195
|
function buildOutline(page, visited = new Set()) {
|
|
@@ -257,7 +260,7 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
257
260
|
attributeNamePrefix: '@_',
|
|
258
261
|
});
|
|
259
262
|
const xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + builder.build(opmlObj);
|
|
260
|
-
|
|
263
|
+
writeTextToPath(outputPath, xml);
|
|
261
264
|
}
|
|
262
265
|
/**
|
|
263
266
|
* Extract strings with metadata for aac-tools-platform compatibility
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AACTree, AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
|
|
2
|
-
import { ProcessorInput } from '../../utils/io';
|
|
2
|
+
import { FileAdapter, ProcessorInput } from '../../utils/io';
|
|
3
3
|
/**
|
|
4
4
|
* Build a map of button IDs to resolved image entries for a specific page.
|
|
5
5
|
* Mirrors the Grid helper for consumers that expect image reference data.
|
|
@@ -16,7 +16,7 @@ export declare function getAllowedImageEntries(tree: AACTree): Set<string>;
|
|
|
16
16
|
* @param entryPath Symbol identifier (e.g., "SYM:12345")
|
|
17
17
|
* @returns Image data buffer or null if not found
|
|
18
18
|
*/
|
|
19
|
-
export declare function openImage(dbOrFile: ProcessorInput, entryPath: string): Buffer | null;
|
|
19
|
+
export declare function openImage(dbOrFile: ProcessorInput, entryPath: string, fileAdapter?: FileAdapter): Buffer | null;
|
|
20
20
|
/**
|
|
21
21
|
* Snap package path information
|
|
22
22
|
*/
|
|
@@ -53,14 +53,14 @@ export interface SnapUsageEntry {
|
|
|
53
53
|
* @param packageNamePattern Optional pattern to filter package names (default: 'TobiiDynavox')
|
|
54
54
|
* @returns Array of Snap package path information
|
|
55
55
|
*/
|
|
56
|
-
export declare function findSnapPackages(packageNamePattern?: string): SnapPackagePath[];
|
|
56
|
+
export declare function findSnapPackages(packageNamePattern?: string, fileAdapter?: FileAdapter): SnapPackagePath[];
|
|
57
57
|
/**
|
|
58
58
|
* Find the first Snap package path matching the pattern
|
|
59
59
|
* Convenience method for when you expect only one Snap installation
|
|
60
60
|
* @param packageNamePattern Optional pattern to filter package names (default: 'TobiiDynavox')
|
|
61
61
|
* @returns Path to the first matching Snap package, or null if not found
|
|
62
62
|
*/
|
|
63
|
-
export declare function findSnapPackagePath(packageNamePattern?: string): string | null;
|
|
63
|
+
export declare function findSnapPackagePath(packageNamePattern?: string, fileAdapter?: FileAdapter): string | null;
|
|
64
64
|
/**
|
|
65
65
|
* Find Snap user directories and their vocab files (.sps/.spb)
|
|
66
66
|
* Typical path:
|
|
@@ -68,14 +68,14 @@ export declare function findSnapPackagePath(packageNamePattern?: string): string
|
|
|
68
68
|
* @param packageNamePattern Optional package filter (default TobiiDynavox)
|
|
69
69
|
* @returns Array of user info with vocab paths
|
|
70
70
|
*/
|
|
71
|
-
export declare function findSnapUsers(packageNamePattern?: string): SnapUserInfo[];
|
|
71
|
+
export declare function findSnapUsers(packageNamePattern?: string, fileAdapter?: FileAdapter): SnapUserInfo[];
|
|
72
72
|
/**
|
|
73
73
|
* Find vocab files for a specific Snap user (or all users)
|
|
74
74
|
* @param userId Optional user identifier filter (case-sensitive directory name)
|
|
75
75
|
* @param packageNamePattern Optional package filter
|
|
76
76
|
* @returns Array of vocab file paths
|
|
77
77
|
*/
|
|
78
|
-
export declare function findSnapUserVocabularies(userId?: string, packageNamePattern?: string): string[];
|
|
78
|
+
export declare function findSnapUserVocabularies(userId?: string, packageNamePattern?: string, fileAdapter?: FileAdapter): string[];
|
|
79
79
|
/**
|
|
80
80
|
* Attempt to find history/analytics files for a Snap user by name
|
|
81
81
|
* Currently searches for files containing "history" under the user directory
|
|
@@ -83,7 +83,7 @@ export declare function findSnapUserVocabularies(userId?: string, packageNamePat
|
|
|
83
83
|
* @param packageNamePattern Optional package filter
|
|
84
84
|
* @returns Array of history file paths (may be empty if not found)
|
|
85
85
|
*/
|
|
86
|
-
export declare function findSnapUserHistory(userId: string, packageNamePattern?: string): string[];
|
|
86
|
+
export declare function findSnapUserHistory(userId: string, packageNamePattern?: string, fileAdapter?: FileAdapter): string[];
|
|
87
87
|
/**
|
|
88
88
|
* Check whether TD Snap appears to be installed (Windows only)
|
|
89
89
|
*/
|
|
@@ -91,7 +91,7 @@ export declare function isSnapInstalled(packageNamePattern?: string): boolean;
|
|
|
91
91
|
/**
|
|
92
92
|
* Read Snap usage history from a pageset file (.sps/.spb)
|
|
93
93
|
*/
|
|
94
|
-
export declare function readSnapUsage(pagesetPath: string): SnapUsageEntry[];
|
|
94
|
+
export declare function readSnapUsage(pagesetPath: string, fileAdapter?: FileAdapter): SnapUsageEntry[];
|
|
95
95
|
/**
|
|
96
96
|
* Read Snap usage history for a user (all pagesets)
|
|
97
97
|
*/
|
|
@@ -1,30 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
-
};
|
|
28
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
3
|
exports.getPageTokenImageMap = getPageTokenImageMap;
|
|
30
4
|
exports.getAllowedImageEntries = getAllowedImageEntries;
|
|
@@ -38,10 +12,9 @@ exports.isSnapInstalled = isSnapInstalled;
|
|
|
38
12
|
exports.readSnapUsage = readSnapUsage;
|
|
39
13
|
exports.readSnapUsageForUser = readSnapUsageForUser;
|
|
40
14
|
const treeStructure_1 = require("../../core/treeStructure");
|
|
41
|
-
const fs = __importStar(require("fs"));
|
|
42
|
-
const path = __importStar(require("path"));
|
|
43
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
44
15
|
const dotnetTicks_1 = require("../../utils/dotnetTicks");
|
|
16
|
+
const io_1 = require("../../utils/io");
|
|
17
|
+
const sqlite_1 = require("../../utils/sqlite");
|
|
45
18
|
// Minimal Snap helpers (stubs) to align with processors/<engine>/helpers pattern
|
|
46
19
|
// NOTE: Snap files can store different types of image data in PageSetData:
|
|
47
20
|
// - PNG/JPEG binaries: Actual images that can be displayed
|
|
@@ -50,7 +23,8 @@ const dotnetTicks_1 = require("../../utils/dotnetTicks");
|
|
|
50
23
|
// We extract PNG/JPEG images but skip vector graphics (requires renderer).
|
|
51
24
|
// NOTE: Snap buttons currently do not populate resolvedImageEntry; these helpers
|
|
52
25
|
// therefore return empty collections until image resolution is implemented.
|
|
53
|
-
function collectFiles(root, matcher, maxDepth = 3) {
|
|
26
|
+
function collectFiles(root, matcher, maxDepth = 3, fileAdapter = io_1.defaultFileAdapter) {
|
|
27
|
+
const { listDir, join, isDirectory } = fileAdapter;
|
|
54
28
|
const results = new Set();
|
|
55
29
|
const stack = [{ dir: root, depth: 0 }];
|
|
56
30
|
while (stack.length > 0) {
|
|
@@ -61,14 +35,14 @@ function collectFiles(root, matcher, maxDepth = 3) {
|
|
|
61
35
|
continue;
|
|
62
36
|
let entries;
|
|
63
37
|
try {
|
|
64
|
-
entries =
|
|
38
|
+
entries = listDir(current.dir);
|
|
65
39
|
}
|
|
66
40
|
catch (error) {
|
|
67
41
|
continue;
|
|
68
42
|
}
|
|
69
43
|
for (const entry of entries) {
|
|
70
|
-
const fullPath =
|
|
71
|
-
if (
|
|
44
|
+
const fullPath = join(current.dir, entry);
|
|
45
|
+
if (isDirectory(entry)) {
|
|
72
46
|
stack.push({ dir: fullPath, depth: current.depth + 1 });
|
|
73
47
|
}
|
|
74
48
|
else if (matcher(fullPath)) {
|
|
@@ -122,17 +96,15 @@ function getAllowedImageEntries(tree) {
|
|
|
122
96
|
* @param entryPath Symbol identifier (e.g., "SYM:12345")
|
|
123
97
|
* @returns Image data buffer or null if not found
|
|
124
98
|
*/
|
|
125
|
-
function openImage(dbOrFile, entryPath) {
|
|
99
|
+
function openImage(dbOrFile, entryPath, fileAdapter = io_1.defaultFileAdapter) {
|
|
100
|
+
const { mkTempDir, join, writeBinaryToPath, removePath, dirname } = fileAdapter;
|
|
126
101
|
let dbPath;
|
|
127
102
|
let cleanupNeeded = false;
|
|
128
103
|
// Handle Buffer input by writing to temp file
|
|
129
104
|
if (Buffer.isBuffer(dbOrFile)) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const tempDir = fs.mkdtempSync(path.join(process.cwd(), 'snap-'));
|
|
134
|
-
dbPath = path.join(tempDir, 'temp.sps');
|
|
135
|
-
fs.writeFileSync(dbPath, dbOrFile);
|
|
105
|
+
const tempDir = mkTempDir(join(process.cwd(), 'snap-'));
|
|
106
|
+
dbPath = join(tempDir, 'temp.sps');
|
|
107
|
+
writeBinaryToPath(dbPath, dbOrFile);
|
|
136
108
|
cleanupNeeded = true;
|
|
137
109
|
}
|
|
138
110
|
else if (typeof dbOrFile === 'string') {
|
|
@@ -141,9 +113,10 @@ function openImage(dbOrFile, entryPath) {
|
|
|
141
113
|
else {
|
|
142
114
|
return null;
|
|
143
115
|
}
|
|
116
|
+
const better_sqlite3 = (0, io_1.getNodeRequire)()('better-sqlite3');
|
|
144
117
|
let db = null;
|
|
145
118
|
try {
|
|
146
|
-
db = new
|
|
119
|
+
db = new better_sqlite3.Database(dbPath, { readonly: true });
|
|
147
120
|
// Query PageSetData for the symbol
|
|
148
121
|
const row = db
|
|
149
122
|
.prepare('SELECT Id, Identifier, Data FROM PageSetData WHERE Identifier = ?')
|
|
@@ -166,9 +139,9 @@ function openImage(dbOrFile, entryPath) {
|
|
|
166
139
|
}
|
|
167
140
|
if (cleanupNeeded && dbPath) {
|
|
168
141
|
try {
|
|
169
|
-
|
|
170
|
-
const dir =
|
|
171
|
-
|
|
142
|
+
removePath(dbPath);
|
|
143
|
+
const dir = dirname(dbPath);
|
|
144
|
+
removePath(dir);
|
|
172
145
|
}
|
|
173
146
|
catch (e) {
|
|
174
147
|
// Ignore cleanup errors
|
|
@@ -182,7 +155,8 @@ function openImage(dbOrFile, entryPath) {
|
|
|
182
155
|
* @param packageNamePattern Optional pattern to filter package names (default: 'TobiiDynavox')
|
|
183
156
|
* @returns Array of Snap package path information
|
|
184
157
|
*/
|
|
185
|
-
function findSnapPackages(packageNamePattern = 'TobiiDynavox') {
|
|
158
|
+
function findSnapPackages(packageNamePattern = 'TobiiDynavox', fileAdapter = io_1.defaultFileAdapter) {
|
|
159
|
+
const { join, listDir, isDirectory, pathExists } = fileAdapter;
|
|
186
160
|
const results = [];
|
|
187
161
|
// Only works on Windows
|
|
188
162
|
if (process.platform !== 'win32') {
|
|
@@ -193,22 +167,22 @@ function findSnapPackages(packageNamePattern = 'TobiiDynavox') {
|
|
|
193
167
|
if (!localAppData) {
|
|
194
168
|
return results;
|
|
195
169
|
}
|
|
196
|
-
const packagesPath =
|
|
170
|
+
const packagesPath = join(localAppData, 'Packages');
|
|
197
171
|
// Check if Packages directory exists
|
|
198
|
-
if (!
|
|
172
|
+
if (!pathExists(packagesPath)) {
|
|
199
173
|
return results;
|
|
200
174
|
}
|
|
201
175
|
// Enumerate packages
|
|
202
|
-
const packages =
|
|
176
|
+
const packages = listDir(packagesPath);
|
|
203
177
|
for (const packageDir of packages) {
|
|
204
|
-
if (!
|
|
178
|
+
if (!isDirectory(packageDir))
|
|
205
179
|
continue;
|
|
206
|
-
const packageName = packageDir
|
|
180
|
+
const packageName = packageDir;
|
|
207
181
|
// Filter by pattern
|
|
208
182
|
if (packageName.includes(packageNamePattern)) {
|
|
209
183
|
results.push({
|
|
210
184
|
packageName,
|
|
211
|
-
packagePath:
|
|
185
|
+
packagePath: join(packagesPath, packageName),
|
|
212
186
|
});
|
|
213
187
|
}
|
|
214
188
|
}
|
|
@@ -224,8 +198,8 @@ function findSnapPackages(packageNamePattern = 'TobiiDynavox') {
|
|
|
224
198
|
* @param packageNamePattern Optional pattern to filter package names (default: 'TobiiDynavox')
|
|
225
199
|
* @returns Path to the first matching Snap package, or null if not found
|
|
226
200
|
*/
|
|
227
|
-
function findSnapPackagePath(packageNamePattern = 'TobiiDynavox') {
|
|
228
|
-
const packages = findSnapPackages(packageNamePattern);
|
|
201
|
+
function findSnapPackagePath(packageNamePattern = 'TobiiDynavox', fileAdapter) {
|
|
202
|
+
const packages = findSnapPackages(packageNamePattern, fileAdapter);
|
|
229
203
|
return packages.length > 0 ? packages[0].packagePath : null;
|
|
230
204
|
}
|
|
231
205
|
/**
|
|
@@ -235,32 +209,33 @@ function findSnapPackagePath(packageNamePattern = 'TobiiDynavox') {
|
|
|
235
209
|
* @param packageNamePattern Optional package filter (default TobiiDynavox)
|
|
236
210
|
* @returns Array of user info with vocab paths
|
|
237
211
|
*/
|
|
238
|
-
function findSnapUsers(packageNamePattern = 'TobiiDynavox') {
|
|
212
|
+
function findSnapUsers(packageNamePattern = 'TobiiDynavox', fileAdapter = io_1.defaultFileAdapter) {
|
|
213
|
+
const { join, listDir, isDirectory, pathExists } = fileAdapter;
|
|
239
214
|
const results = [];
|
|
240
215
|
if (process.platform !== 'win32') {
|
|
241
216
|
return results;
|
|
242
217
|
}
|
|
243
|
-
const packagePath = findSnapPackagePath(packageNamePattern);
|
|
218
|
+
const packagePath = findSnapPackagePath(packageNamePattern, fileAdapter);
|
|
244
219
|
if (!packagePath) {
|
|
245
220
|
return results;
|
|
246
221
|
}
|
|
247
|
-
const usersRoot =
|
|
248
|
-
if (!
|
|
222
|
+
const usersRoot = join(packagePath, 'LocalState', 'Users');
|
|
223
|
+
if (!pathExists(usersRoot)) {
|
|
249
224
|
return results;
|
|
250
225
|
}
|
|
251
|
-
const entries =
|
|
226
|
+
const entries = listDir(usersRoot);
|
|
252
227
|
for (const entry of entries) {
|
|
253
|
-
if (!
|
|
228
|
+
if (!isDirectory(entry))
|
|
254
229
|
continue;
|
|
255
|
-
if (entry.
|
|
230
|
+
if (entry.toLowerCase().startsWith('swiftkey'))
|
|
256
231
|
continue;
|
|
257
|
-
const userPath =
|
|
232
|
+
const userPath = join(usersRoot, entry);
|
|
258
233
|
const vocabPaths = collectFiles(userPath, (full) => {
|
|
259
|
-
const ext =
|
|
234
|
+
const ext = (0, io_1.extname)(full).toLowerCase();
|
|
260
235
|
return ext === '.sps' || ext === '.spb';
|
|
261
|
-
}, 2);
|
|
236
|
+
}, 2, fileAdapter);
|
|
262
237
|
results.push({
|
|
263
|
-
userId: entry
|
|
238
|
+
userId: entry,
|
|
264
239
|
userPath,
|
|
265
240
|
vocabPaths,
|
|
266
241
|
});
|
|
@@ -273,8 +248,8 @@ function findSnapUsers(packageNamePattern = 'TobiiDynavox') {
|
|
|
273
248
|
* @param packageNamePattern Optional package filter
|
|
274
249
|
* @returns Array of vocab file paths
|
|
275
250
|
*/
|
|
276
|
-
function findSnapUserVocabularies(userId, packageNamePattern = 'TobiiDynavox') {
|
|
277
|
-
const users = findSnapUsers(packageNamePattern).filter((u) => !userId || u.userId === userId);
|
|
251
|
+
function findSnapUserVocabularies(userId, packageNamePattern = 'TobiiDynavox', fileAdapter) {
|
|
252
|
+
const users = findSnapUsers(packageNamePattern, fileAdapter).filter((u) => !userId || u.userId === userId);
|
|
278
253
|
return users.flatMap((u) => u.vocabPaths);
|
|
279
254
|
}
|
|
280
255
|
/**
|
|
@@ -284,11 +259,12 @@ function findSnapUserVocabularies(userId, packageNamePattern = 'TobiiDynavox') {
|
|
|
284
259
|
* @param packageNamePattern Optional package filter
|
|
285
260
|
* @returns Array of history file paths (may be empty if not found)
|
|
286
261
|
*/
|
|
287
|
-
function findSnapUserHistory(userId, packageNamePattern = 'TobiiDynavox') {
|
|
288
|
-
const
|
|
262
|
+
function findSnapUserHistory(userId, packageNamePattern = 'TobiiDynavox', fileAdapter = io_1.defaultFileAdapter) {
|
|
263
|
+
const { basename } = fileAdapter;
|
|
264
|
+
const user = findSnapUsers(packageNamePattern, fileAdapter).find((u) => u.userId === userId);
|
|
289
265
|
if (!user)
|
|
290
266
|
return [];
|
|
291
|
-
return collectFiles(user.userPath, (full) =>
|
|
267
|
+
return collectFiles(user.userPath, (full) => basename(full).toLowerCase().includes('history'), 2, fileAdapter);
|
|
292
268
|
}
|
|
293
269
|
/**
|
|
294
270
|
* Check whether TD Snap appears to be installed (Windows only)
|
|
@@ -301,10 +277,12 @@ function isSnapInstalled(packageNamePattern = 'TobiiDynavox') {
|
|
|
301
277
|
/**
|
|
302
278
|
* Read Snap usage history from a pageset file (.sps/.spb)
|
|
303
279
|
*/
|
|
304
|
-
function readSnapUsage(pagesetPath) {
|
|
305
|
-
|
|
280
|
+
function readSnapUsage(pagesetPath, fileAdapter = io_1.defaultFileAdapter) {
|
|
281
|
+
const { pathExists } = fileAdapter;
|
|
282
|
+
if (!pathExists(pagesetPath))
|
|
306
283
|
return [];
|
|
307
|
-
const
|
|
284
|
+
const Database = (0, sqlite_1.requireBetterSqlite3)();
|
|
285
|
+
const db = new Database(pagesetPath, { readonly: true });
|
|
308
286
|
const tableCheck = db
|
|
309
287
|
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('ButtonUsage','Button')")
|
|
310
288
|
.all();
|