@willwade/aac-processors 0.1.21 → 0.2.1

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.
Files changed (87) hide show
  1. package/README.md +3 -2
  2. package/dist/browser/processors/applePanelsProcessor.js +24 -24
  3. package/dist/browser/processors/astericsGridProcessor.js +22 -24
  4. package/dist/browser/processors/dotProcessor.js +6 -10
  5. package/dist/browser/processors/gridset/helpers.js +33 -30
  6. package/dist/browser/processors/gridset/symbolExtractor.js +2 -2
  7. package/dist/browser/processors/gridset/symbolSearch.js +22 -22
  8. package/dist/browser/processors/gridset/symbols.js +14 -14
  9. package/dist/browser/processors/gridsetProcessor.js +7 -7
  10. package/dist/browser/processors/obfProcessor.js +54 -47
  11. package/dist/browser/processors/opmlProcessor.js +6 -10
  12. package/dist/browser/processors/snap/helpers.js +34 -30
  13. package/dist/browser/processors/snapProcessor.js +28 -28
  14. package/dist/browser/processors/touchchatProcessor.js +24 -25
  15. package/dist/browser/utilities/analytics/history.js +24 -18
  16. package/dist/browser/utilities/analytics/metrics/comparison.js +16 -16
  17. package/dist/browser/utilities/analytics/metrics/vocabulary.js +2 -2
  18. package/dist/browser/utilities/analytics/reference/browser.js +16 -16
  19. package/dist/browser/utilities/analytics/reference/index.js +25 -24
  20. package/dist/browser/utils/io.js +29 -25
  21. package/dist/browser/utils/sqlite.js +5 -5
  22. package/dist/browser/utils/zip.js +2 -4
  23. package/dist/browser/validation/gridsetValidator.js +2 -2
  24. package/dist/browser/validation/obfValidator.js +2 -2
  25. package/dist/browser/validation/snapValidator.js +3 -3
  26. package/dist/browser/validation/touchChatValidator.js +3 -3
  27. package/dist/cli/index.js +19 -16
  28. package/dist/core/baseProcessor.d.ts +1 -1
  29. package/dist/processors/applePanelsProcessor.js +24 -24
  30. package/dist/processors/astericsGridProcessor.d.ts +4 -4
  31. package/dist/processors/astericsGridProcessor.js +22 -24
  32. package/dist/processors/dotProcessor.js +6 -10
  33. package/dist/processors/excelProcessor.d.ts +3 -3
  34. package/dist/processors/excelProcessor.js +10 -13
  35. package/dist/processors/gridset/helpers.d.ts +9 -9
  36. package/dist/processors/gridset/helpers.js +33 -30
  37. package/dist/processors/gridset/symbolExtractor.d.ts +1 -1
  38. package/dist/processors/gridset/symbolExtractor.js +2 -2
  39. package/dist/processors/gridset/symbolSearch.d.ts +10 -10
  40. package/dist/processors/gridset/symbolSearch.js +22 -22
  41. package/dist/processors/gridset/symbols.d.ts +3 -3
  42. package/dist/processors/gridset/symbols.js +14 -14
  43. package/dist/processors/gridsetProcessor.d.ts +2 -2
  44. package/dist/processors/gridsetProcessor.js +7 -7
  45. package/dist/processors/obfProcessor.d.ts +2 -2
  46. package/dist/processors/obfProcessor.js +54 -47
  47. package/dist/processors/obfsetProcessor.js +1 -2
  48. package/dist/processors/opmlProcessor.js +6 -10
  49. package/dist/processors/snap/helpers.d.ts +8 -8
  50. package/dist/processors/snap/helpers.js +34 -30
  51. package/dist/processors/snapProcessor.d.ts +2 -2
  52. package/dist/processors/snapProcessor.js +28 -28
  53. package/dist/processors/touchchatProcessor.d.ts +2 -2
  54. package/dist/processors/touchchatProcessor.js +24 -25
  55. package/dist/types/aac.d.ts +2 -2
  56. package/dist/utilities/analytics/history.d.ts +8 -8
  57. package/dist/utilities/analytics/history.js +24 -18
  58. package/dist/utilities/analytics/index.d.ts +1 -1
  59. package/dist/utilities/analytics/index.js +3 -2
  60. package/dist/utilities/analytics/metrics/comparison.d.ts +1 -1
  61. package/dist/utilities/analytics/metrics/comparison.js +16 -16
  62. package/dist/utilities/analytics/metrics/vocabulary.d.ts +1 -1
  63. package/dist/utilities/analytics/metrics/vocabulary.js +2 -2
  64. package/dist/utilities/analytics/reference/browser.d.ts +9 -9
  65. package/dist/utilities/analytics/reference/browser.js +16 -16
  66. package/dist/utilities/analytics/reference/index.d.ts +21 -21
  67. package/dist/utilities/analytics/reference/index.js +25 -24
  68. package/dist/utilities/symbolTools.d.ts +5 -5
  69. package/dist/utilities/symbolTools.js +10 -8
  70. package/dist/utils/io.d.ts +11 -11
  71. package/dist/utils/io.js +29 -25
  72. package/dist/utils/sqlite.d.ts +1 -1
  73. package/dist/utils/sqlite.js +5 -5
  74. package/dist/utils/zip.js +2 -4
  75. package/dist/validation/applePanelsValidator.js +7 -6
  76. package/dist/validation/astericsValidator.js +2 -2
  77. package/dist/validation/dotValidator.js +2 -2
  78. package/dist/validation/excelValidator.js +2 -2
  79. package/dist/validation/gridsetValidator.js +2 -2
  80. package/dist/validation/index.js +2 -2
  81. package/dist/validation/obfValidator.js +2 -2
  82. package/dist/validation/obfsetValidator.js +2 -2
  83. package/dist/validation/opmlValidator.js +2 -2
  84. package/dist/validation/snapValidator.js +3 -3
  85. package/dist/validation/touchChatValidator.js +3 -3
  86. package/docs/BROWSER_USAGE.md +0 -40
  87. package/package.json +1 -1
package/README.md CHANGED
@@ -63,9 +63,9 @@ const tree = await processor.loadIntoTree(gridsetUint8Array);
63
63
  - Asterics Grid
64
64
  - Excel export
65
65
 
66
- ## Translation Workflow
66
+ ## Wrangle strings workflow
67
67
 
68
- All processors implement `processTexts()` for translation use cases.
68
+ All processors implement `processTexts()` to get all strings eg
69
69
 
70
70
  ```ts
71
71
  import { DotProcessor } from '@willwade/aac-processors';
@@ -80,6 +80,7 @@ const translations = new Map([
80
80
 
81
81
  await processor.processTexts('board.dot', translations, 'board-es.dot');
82
82
  ```
83
+ NB: Please use [https://aactools.co.uk](https://aactools.co.uk) for a far more comphrensive translation logic - where we do far far more than this...
83
84
 
84
85
  ## Documentation
85
86
 
@@ -86,15 +86,14 @@ class ApplePanelsProcessor extends BaseProcessor {
86
86
  }
87
87
  async loadIntoTree(filePathOrBuffer) {
88
88
  const { readBinaryFromInput, readTextFromInput, pathExists, getFileSize, join } = this.options.fileAdapter;
89
- await Promise.resolve();
90
89
  const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.plist';
91
90
  let buffer;
92
91
  try {
93
92
  if (typeof filePathOrBuffer === 'string') {
94
93
  if (filePathOrBuffer.endsWith('.ascconfig')) {
95
94
  const panelDefsPath = join(filePathOrBuffer, 'Contents', 'Resources', 'PanelDefinitions.plist');
96
- if (pathExists(panelDefsPath)) {
97
- buffer = readBinaryFromInput(panelDefsPath);
95
+ if (await pathExists(panelDefsPath)) {
96
+ buffer = await readBinaryFromInput(panelDefsPath);
98
97
  }
99
98
  else {
100
99
  const validation = buildValidationResultFromMessage({
@@ -109,13 +108,13 @@ class ApplePanelsProcessor extends BaseProcessor {
109
108
  }
110
109
  }
111
110
  else {
112
- buffer = readBinaryFromInput(filePathOrBuffer);
111
+ buffer = await readBinaryFromInput(filePathOrBuffer);
113
112
  }
114
113
  }
115
114
  else {
116
- buffer = readBinaryFromInput(filePathOrBuffer);
115
+ buffer = await readBinaryFromInput(filePathOrBuffer);
117
116
  }
118
- const content = readTextFromInput(buffer);
117
+ const content = await readTextFromInput(buffer);
119
118
  const parsedData = plist.parse(content);
120
119
  let panelsData = [];
121
120
  if (Array.isArray(parsedData.panels)) {
@@ -244,10 +243,12 @@ class ApplePanelsProcessor extends BaseProcessor {
244
243
  const validation = buildValidationResultFromMessage({
245
244
  filename,
246
245
  filesize: typeof filePathOrBuffer === 'string'
247
- ? (() => {
248
- return pathExists(filePathOrBuffer) ? getFileSize(filePathOrBuffer) : 0;
246
+ ? await (async () => {
247
+ return (await pathExists(filePathOrBuffer))
248
+ ? await getFileSize(filePathOrBuffer)
249
+ : 0;
249
250
  })()
250
- : readBinaryFromInput(filePathOrBuffer).byteLength,
251
+ : (await readBinaryFromInput(filePathOrBuffer)).byteLength,
251
252
  format: 'applepanels',
252
253
  message: err?.message || 'Failed to parse Apple Panels file',
253
254
  type: 'parse',
@@ -307,15 +308,14 @@ class ApplePanelsProcessor extends BaseProcessor {
307
308
  // Save the translated tree to the requested location and return its content
308
309
  await this.saveFromTree(tree, outputPath);
309
310
  if (outputPath.endsWith('.plist')) {
310
- return readBinaryFromInput(outputPath);
311
+ return await readBinaryFromInput(outputPath);
311
312
  }
312
313
  const configPath = outputPath.endsWith('.ascconfig') ? outputPath : `${outputPath}.ascconfig`;
313
314
  const panelDefsPath = join(configPath, 'Contents', 'Resources', 'PanelDefinitions.plist');
314
- return readBinaryFromInput(panelDefsPath);
315
+ return await readBinaryFromInput(panelDefsPath);
315
316
  }
316
317
  async saveFromTree(tree, outputPath) {
317
318
  const { writeTextToPath, pathExists, mkDir, join, dirname } = this.options.fileAdapter;
318
- await Promise.resolve();
319
319
  // Support two output modes:
320
320
  // 1) Single-file .plist (PanelDefinitions.plist content written directly)
321
321
  // 2) Apple Panels bundle folder (*.ascconfig) with Contents/Resources structure
@@ -328,12 +328,12 @@ class ApplePanelsProcessor extends BaseProcessor {
328
328
  configPath = outputPath.endsWith('.ascconfig') ? outputPath : `${outputPath}.ascconfig`;
329
329
  contentsPath = join(configPath, 'Contents');
330
330
  resourcesPath = join(contentsPath, 'Resources');
331
- if (!pathExists(configPath))
332
- mkDir(configPath, { recursive: true });
333
- if (!pathExists(contentsPath))
334
- mkDir(contentsPath, { recursive: true });
335
- if (!pathExists(resourcesPath))
336
- mkDir(resourcesPath, { recursive: true });
331
+ if (!(await pathExists(configPath)))
332
+ await mkDir(configPath, { recursive: true });
333
+ if (!(await pathExists(contentsPath)))
334
+ await mkDir(contentsPath, { recursive: true });
335
+ if (!(await pathExists(resourcesPath)))
336
+ await mkDir(resourcesPath, { recursive: true });
337
337
  // Create Info.plist (bundle mode only)
338
338
  const infoPlist = {
339
339
  ASCConfigurationDisplayName: tree.metadata?.name || 'AAC Processors Export',
@@ -349,10 +349,10 @@ class ApplePanelsProcessor extends BaseProcessor {
349
349
  `Generated by AAC Processors${tree.metadata?.author ? ` - Author: ${tree.metadata.author}` : ''}`,
350
350
  };
351
351
  const infoPlistContent = plist.build(infoPlist);
352
- writeTextToPath(join(contentsPath, 'Info.plist'), infoPlistContent);
352
+ await writeTextToPath(join(contentsPath, 'Info.plist'), infoPlistContent);
353
353
  // Create AssetIndex.plist (empty)
354
354
  const assetIndexContent = plist.build({});
355
- writeTextToPath(join(resourcesPath, 'AssetIndex.plist'), assetIndexContent);
355
+ await writeTextToPath(join(resourcesPath, 'AssetIndex.plist'), assetIndexContent);
356
356
  }
357
357
  // Build PanelDefinitions content from tree
358
358
  const panelsDict = {};
@@ -479,13 +479,13 @@ class ApplePanelsProcessor extends BaseProcessor {
479
479
  if (isSinglePlist) {
480
480
  // Write single PanelDefinitions.plist file directly
481
481
  const dir = dirname(outputPath);
482
- if (!pathExists(dir))
483
- mkDir(dir, { recursive: true });
484
- writeTextToPath(outputPath, panelDefsContent);
482
+ if (!(await pathExists(dir)))
483
+ await mkDir(dir, { recursive: true });
484
+ await writeTextToPath(outputPath, panelDefsContent);
485
485
  }
486
486
  else {
487
487
  // Write into bundle structure
488
- writeTextToPath(join(resourcesPath, 'PanelDefinitions.plist'), panelDefsContent);
488
+ await writeTextToPath(join(resourcesPath, 'PanelDefinitions.plist'), panelDefsContent);
489
489
  }
490
490
  }
491
491
  createApplePanelsAction(button) {
@@ -562,7 +562,7 @@ class AstericsGridProcessor extends BaseProcessor {
562
562
  });
563
563
  }
564
564
  // Also extract texts from the raw file for comprehensive coverage
565
- const rawTexts = this.extractRawTexts(filePathOrBuffer);
565
+ const rawTexts = await this.extractRawTexts(filePathOrBuffer);
566
566
  rawTexts.forEach((text) => {
567
567
  if (text && !texts.includes(text)) {
568
568
  texts.push(text);
@@ -570,9 +570,9 @@ class AstericsGridProcessor extends BaseProcessor {
570
570
  });
571
571
  return texts;
572
572
  }
573
- extractRawTexts(filePathOrBuffer) {
573
+ async extractRawTexts(filePathOrBuffer) {
574
574
  const { readTextFromInput } = this.options.fileAdapter;
575
- let content = readTextFromInput(filePathOrBuffer);
575
+ let content = await readTextFromInput(filePathOrBuffer);
576
576
  // Remove BOM if present
577
577
  if (content.charCodeAt(0) === 0xfeff) {
578
578
  content = content.slice(1);
@@ -653,12 +653,11 @@ class AstericsGridProcessor extends BaseProcessor {
653
653
  }
654
654
  async loadIntoTree(filePathOrBuffer) {
655
655
  const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
656
- await Promise.resolve();
657
656
  const tree = new AACTree();
658
657
  const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.grd';
659
- const buffer = readBinaryFromInput(filePathOrBuffer);
658
+ const buffer = await readBinaryFromInput(filePathOrBuffer);
660
659
  try {
661
- let content = readTextFromInput(buffer);
660
+ let content = await readTextFromInput(buffer);
662
661
  // Remove BOM if present
663
662
  if (content.charCodeAt(0) === 0xfeff) {
664
663
  content = content.slice(1);
@@ -1052,8 +1051,7 @@ class AstericsGridProcessor extends BaseProcessor {
1052
1051
  }
1053
1052
  async processTexts(filePathOrBuffer, translations, outputPath) {
1054
1053
  const { readTextFromInput, readBinaryFromInput, writeTextToPath } = this.options.fileAdapter;
1055
- await Promise.resolve();
1056
- let content = readTextFromInput(filePathOrBuffer);
1054
+ let content = await readTextFromInput(filePathOrBuffer);
1057
1055
  // Remove BOM if present
1058
1056
  if (content.charCodeAt(0) === 0xfeff) {
1059
1057
  content = content.slice(1);
@@ -1062,8 +1060,8 @@ class AstericsGridProcessor extends BaseProcessor {
1062
1060
  // Apply translations directly to the JSON structure for comprehensive coverage
1063
1061
  this.applyTranslationsToGridFile(grdFile, translations);
1064
1062
  // Write the translated file
1065
- writeTextToPath(outputPath, JSON.stringify(grdFile, null, 2));
1066
- return readBinaryFromInput(outputPath);
1063
+ await writeTextToPath(outputPath, JSON.stringify(grdFile, null, 2));
1064
+ return await readBinaryFromInput(outputPath);
1067
1065
  }
1068
1066
  applyTranslationsToGridFile(grdFile, translations) {
1069
1067
  grdFile.grids.forEach((grid) => {
@@ -1176,7 +1174,6 @@ class AstericsGridProcessor extends BaseProcessor {
1176
1174
  }
1177
1175
  async saveFromTree(tree, outputPath) {
1178
1176
  const { writeTextToPath } = this.options.fileAdapter;
1179
- await Promise.resolve();
1180
1177
  // Use default Asterics Grid styling instead of taking from first page
1181
1178
  // This prevents issues where the first page has unusual colors (like purple)
1182
1179
  const defaultPageStyle = {
@@ -1379,14 +1376,14 @@ class AstericsGridProcessor extends BaseProcessor {
1379
1376
  },
1380
1377
  },
1381
1378
  };
1382
- writeTextToPath(outputPath, JSON.stringify(grdFile, null, 2));
1379
+ await writeTextToPath(outputPath, JSON.stringify(grdFile, null, 2));
1383
1380
  }
1384
1381
  /**
1385
1382
  * Add audio recording to a specific grid element
1386
1383
  */
1387
- addAudioToElement(filePath, elementId, audioData, metadata) {
1384
+ async addAudioToElement(filePath, elementId, audioData, metadata) {
1388
1385
  const { readTextFromInput, writeTextToPath } = this.options.fileAdapter;
1389
- let content = readTextFromInput(filePath);
1386
+ let content = await readTextFromInput(filePath);
1390
1387
  // Remove BOM if present
1391
1388
  if (content.charCodeAt(0) === 0xfeff) {
1392
1389
  content = content.slice(1);
@@ -1429,32 +1426,33 @@ class AstericsGridProcessor extends BaseProcessor {
1429
1426
  throw new Error(`Element with ID ${elementId} not found`);
1430
1427
  }
1431
1428
  // Write back to file
1432
- writeTextToPath(filePath, JSON.stringify(grdFile, null, 2));
1429
+ await writeTextToPath(filePath, JSON.stringify(grdFile, null, 2));
1433
1430
  }
1434
1431
  /**
1435
1432
  * Create a copy of the grid file with audio recordings added
1436
1433
  */
1437
- createAudioEnhancedGridFile(sourceFilePath, targetFilePath, audioMappings) {
1434
+ async createAudioEnhancedGridFile(sourceFilePath, targetFilePath, audioMappings) {
1438
1435
  const { writeBinaryToPath, readBinaryFromInput } = this.options.fileAdapter;
1439
1436
  // Copy the source file to target
1440
- writeBinaryToPath(targetFilePath, readBinaryFromInput(sourceFilePath));
1437
+ await writeBinaryToPath(targetFilePath, await readBinaryFromInput(sourceFilePath));
1441
1438
  // Add audio recordings to the copy
1442
- audioMappings.forEach((audioInfo, elementId) => {
1439
+ await Promise.all(Object.entries(audioMappings).map(async (item) => {
1440
+ const [elementId, audioInfo] = item;
1443
1441
  try {
1444
- this.addAudioToElement(targetFilePath, elementId, audioInfo.audioData, audioInfo.metadata);
1442
+ await this.addAudioToElement(targetFilePath, elementId, audioInfo.audioData, audioInfo.metadata);
1445
1443
  }
1446
1444
  catch (error) {
1447
1445
  // Failed to add audio to element - continue with others
1448
1446
  console.warn(`Failed to add audio to element ${elementId}:`, error);
1449
1447
  }
1450
- });
1448
+ }));
1451
1449
  }
1452
1450
  /**
1453
1451
  * Extract all element IDs from the grid file for audio mapping
1454
1452
  */
1455
- getElementIds(filePathOrBuffer) {
1453
+ async getElementIds(filePathOrBuffer) {
1456
1454
  const { readTextFromInput } = this.options.fileAdapter;
1457
- let content = readTextFromInput(filePathOrBuffer);
1455
+ let content = await readTextFromInput(filePathOrBuffer);
1458
1456
  // Remove BOM if present
1459
1457
  if (content.charCodeAt(0) === 0xfeff) {
1460
1458
  content = content.slice(1);
@@ -1476,9 +1474,9 @@ class AstericsGridProcessor extends BaseProcessor {
1476
1474
  /**
1477
1475
  * Check if an element has audio recording
1478
1476
  */
1479
- hasAudioRecording(filePathOrBuffer, elementId) {
1477
+ async hasAudioRecording(filePathOrBuffer, elementId) {
1480
1478
  const { readTextFromInput } = this.options.fileAdapter;
1481
- let content = readTextFromInput(filePathOrBuffer);
1479
+ let content = await readTextFromInput(filePathOrBuffer);
1482
1480
  // Remove BOM if present
1483
1481
  if (content.charCodeAt(0) === 0xfeff) {
1484
1482
  content = content.slice(1);
@@ -49,8 +49,7 @@ class DotProcessor extends BaseProcessor {
49
49
  }
50
50
  async extractTexts(filePathOrBuffer) {
51
51
  const { readTextFromInput } = this.options.fileAdapter;
52
- await Promise.resolve();
53
- const content = readTextFromInput(filePathOrBuffer);
52
+ const content = await readTextFromInput(filePathOrBuffer);
54
53
  const { nodes, edges } = this.parseDotFile(content);
55
54
  const texts = [];
56
55
  // Collect node labels
@@ -67,12 +66,11 @@ class DotProcessor extends BaseProcessor {
67
66
  }
68
67
  async loadIntoTree(filePathOrBuffer) {
69
68
  const { readBinaryFromInput, readTextFromInput } = this.options.fileAdapter;
70
- await Promise.resolve();
71
69
  const filename = typeof filePathOrBuffer === 'string' ? getBasename(filePathOrBuffer) : 'upload.dot';
72
- const buffer = readBinaryFromInput(filePathOrBuffer);
70
+ const buffer = await readBinaryFromInput(filePathOrBuffer);
73
71
  const filesize = buffer.byteLength;
74
72
  try {
75
- const content = readTextFromInput(buffer);
73
+ const content = await readTextFromInput(buffer);
76
74
  if (!content || content.trim().length === 0) {
77
75
  const validation = buildValidationResultFromMessage({
78
76
  filename,
@@ -157,8 +155,7 @@ class DotProcessor extends BaseProcessor {
157
155
  }
158
156
  async processTexts(filePathOrBuffer, translations, outputPath) {
159
157
  const { readTextFromInput, writeBinaryToPath } = this.options.fileAdapter;
160
- await Promise.resolve();
161
- const content = readTextFromInput(filePathOrBuffer);
158
+ const content = await readTextFromInput(filePathOrBuffer);
162
159
  let translatedContent = content;
163
160
  translations.forEach((translation, text) => {
164
161
  if (typeof text === 'string' && typeof translation === 'string') {
@@ -169,12 +166,11 @@ class DotProcessor extends BaseProcessor {
169
166
  }
170
167
  });
171
168
  const resultBuffer = encodeText(translatedContent || '');
172
- writeBinaryToPath(outputPath, resultBuffer);
169
+ await writeBinaryToPath(outputPath, resultBuffer);
173
170
  return resultBuffer;
174
171
  }
175
172
  async saveFromTree(tree, _outputPath) {
176
173
  const { writeTextToPath } = this.options.fileAdapter;
177
- await Promise.resolve();
178
174
  let dotContent = `digraph "${tree.metadata?.name || 'AACBoard'}" {\n`;
179
175
  // Helper to escape DOT string
180
176
  const escapeDotString = (str) => {
@@ -204,7 +200,7 @@ class DotProcessor extends BaseProcessor {
204
200
  });
205
201
  }
206
202
  dotContent += '}\n';
207
- writeTextToPath(_outputPath, dotContent);
203
+ await writeTextToPath(_outputPath, dotContent);
208
204
  }
209
205
  /**
210
206
  * Extract strings with metadata for aac-tools-platform compatibility
@@ -170,7 +170,7 @@ export function getCommonDocumentsPath() {
170
170
  * C:\Users\Public\Documents\Smartbox\Grid 3\Users\{UserName}\Grid Sets\
171
171
  * @returns Array of Grid3 user path information
172
172
  */
173
- export function findGrid3UserPaths(fileAdapter = defaultFileAdapter) {
173
+ export async function findGrid3UserPaths(fileAdapter = defaultFileAdapter) {
174
174
  const { pathExists, listDir, isDirectory } = fileAdapter;
175
175
  const results = [];
176
176
  // Only works on Windows
@@ -182,26 +182,26 @@ export function findGrid3UserPaths(fileAdapter = defaultFileAdapter) {
182
182
  // Use Windows path joining so tests that mock a Windows platform stay consistent even on POSIX runners
183
183
  const grid3BasePath = joinWin32(commonDocs, 'Smartbox', 'Grid 3', 'Users');
184
184
  // Check if Grid3 Users directory exists
185
- if (!pathExists(grid3BasePath)) {
185
+ if (!(await pathExists(grid3BasePath))) {
186
186
  return results;
187
187
  }
188
188
  // Enumerate users
189
- const users = listDir(grid3BasePath);
189
+ const users = await listDir(grid3BasePath);
190
190
  for (const userDir of users) {
191
- if (!isDirectory(userDir))
191
+ if (!(await isDirectory(userDir)))
192
192
  continue;
193
193
  const userName = userDir;
194
194
  const userPath = joinWin32(grid3BasePath, userName);
195
195
  // Enumerate language codes
196
- const langDirs = listDir(userPath);
196
+ const langDirs = await listDir(userPath);
197
197
  for (const langDir of langDirs) {
198
- if (!isDirectory(langDir))
198
+ if (!(await isDirectory(langDir)))
199
199
  continue;
200
200
  const langCode = langDir;
201
201
  const basePath = joinWin32(userPath, langCode);
202
202
  const historyDbPath = joinWin32(basePath, 'Phrases', 'history.sqlite');
203
203
  // Only include if history database exists
204
- if (pathExists(historyDbPath)) {
204
+ if (await pathExists(historyDbPath)) {
205
205
  results.push({
206
206
  userName,
207
207
  langCode,
@@ -222,21 +222,22 @@ export function findGrid3UserPaths(fileAdapter = defaultFileAdapter) {
222
222
  * Convenience method that returns just the database file paths
223
223
  * @returns Array of paths to history.sqlite files
224
224
  */
225
- export function findGrid3HistoryDatabases(fileAdapter) {
226
- return findGrid3UserPaths(fileAdapter).map((userPath) => userPath.historyDbPath);
225
+ export async function findGrid3HistoryDatabases(fileAdapter) {
226
+ const userPaths = await findGrid3UserPaths(fileAdapter);
227
+ return userPaths.map((userPath) => userPath.historyDbPath);
227
228
  }
228
229
  /**
229
230
  * Get Grid 3 users (alias of findGrid3UserPaths for clarity)
230
231
  */
231
- export function findGrid3Users() {
232
- return findGrid3UserPaths();
232
+ export async function findGrid3Users() {
233
+ return await findGrid3UserPaths();
233
234
  }
234
235
  /**
235
236
  * Find Grid 3 gridset/vocabulary files for each user
236
237
  * @param userName Optional user filter; matches case-insensitively
237
238
  * @returns Array of user/gridset path pairs
238
239
  */
239
- export function findGrid3Vocabularies(userName, fileAdapter = defaultFileAdapter) {
240
+ export async function findGrid3Vocabularies(userName, fileAdapter = defaultFileAdapter) {
240
241
  const { pathExists, listDir, isDirectory } = fileAdapter;
241
242
  const results = [];
242
243
  if (process.platform !== 'win32') {
@@ -244,23 +245,23 @@ export function findGrid3Vocabularies(userName, fileAdapter = defaultFileAdapter
244
245
  }
245
246
  const commonDocs = getCommonDocumentsPath();
246
247
  const grid3BasePath = joinWin32(commonDocs, 'Smartbox', 'Grid 3', 'Users');
247
- if (!pathExists(grid3BasePath)) {
248
+ if (!(await pathExists(grid3BasePath))) {
248
249
  return results;
249
250
  }
250
251
  const normalizedUser = userName?.toLowerCase();
251
- const users = listDir(grid3BasePath);
252
+ const users = await listDir(grid3BasePath);
252
253
  for (const userDir of users) {
253
- if (!isDirectory(userDir))
254
+ if (!(await isDirectory(userDir)))
254
255
  continue;
255
256
  if (normalizedUser && userDir.toLowerCase() !== normalizedUser)
256
257
  continue;
257
258
  const userRoot = joinWin32(grid3BasePath, userDir);
258
259
  const gridSetsDir = joinWin32(userRoot, 'Grid Sets');
259
- if (!pathExists(gridSetsDir))
260
+ if (!(await pathExists(gridSetsDir)))
260
261
  continue;
261
- const entries = listDir(gridSetsDir);
262
+ const entries = await listDir(gridSetsDir);
262
263
  for (const entry of entries) {
263
- if (!pathExists(entry) || isDirectory(entry))
264
+ if (!(await pathExists(entry)) || (await isDirectory(entry)))
264
265
  continue;
265
266
  const ext = extname(entry).toLowerCase();
266
267
  if (ext === '.gridset' || ext === '.gridsetx' || ext === '.grd' || ext === '.grdl') {
@@ -279,19 +280,20 @@ export function findGrid3Vocabularies(userName, fileAdapter = defaultFileAdapter
279
280
  * @param langCode Optional language code filter (case-insensitive)
280
281
  * @returns Path to history.sqlite or null if not found
281
282
  */
282
- export function findGrid3UserHistory(userName, langCode, fileAdapter) {
283
+ export async function findGrid3UserHistory(userName, langCode, fileAdapter) {
283
284
  if (!userName)
284
285
  return null;
285
286
  const normalizedUser = userName.toLowerCase();
286
287
  const normalizedLang = langCode?.toLowerCase();
287
- const match = findGrid3UserPaths(fileAdapter).find((u) => u.userName.toLowerCase() === normalizedUser &&
288
+ const userPaths = await findGrid3UserPaths(fileAdapter);
289
+ const match = userPaths.find((u) => u.userName.toLowerCase() === normalizedUser &&
288
290
  (!normalizedLang || u.langCode.toLowerCase() === normalizedLang));
289
291
  return match?.historyDbPath ?? null;
290
292
  }
291
293
  /**
292
294
  * Check whether Grid 3 appears to be installed (Windows only)
293
295
  */
294
- export function isGrid3Installed(fileAdapter = defaultFileAdapter) {
296
+ export async function isGrid3Installed(fileAdapter = defaultFileAdapter) {
295
297
  const { pathExists } = fileAdapter;
296
298
  if (process.platform !== 'win32')
297
299
  return false;
@@ -299,7 +301,7 @@ export function isGrid3Installed(fileAdapter = defaultFileAdapter) {
299
301
  if (!commonDocs)
300
302
  return false;
301
303
  const grid3BasePath = joinWin32(commonDocs, 'Smartbox', 'Grid 3', 'Users');
302
- return pathExists(grid3BasePath);
304
+ return await pathExists(grid3BasePath);
303
305
  }
304
306
  function parseGrid3ContentXml(xmlContent) {
305
307
  const regex = /<r>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?<\/r>/gis;
@@ -318,9 +320,9 @@ function parseGrid3ContentXml(xmlContent) {
318
320
  * @param historyDbPath Absolute path to the history database
319
321
  * @returns Parsed history entries grouped by phrase
320
322
  */
321
- export function readGrid3History(historyDbPath, fileAdapter = defaultFileAdapter) {
323
+ export async function readGrid3History(historyDbPath, fileAdapter = defaultFileAdapter) {
322
324
  const { pathExists } = fileAdapter;
323
- if (!pathExists(historyDbPath))
325
+ if (!(await pathExists(historyDbPath)))
324
326
  return [];
325
327
  const Database = requireBetterSqlite3();
326
328
  const db = new Database(historyDbPath, { readonly: true });
@@ -380,17 +382,18 @@ export function readGrid3History(historyDbPath, fileAdapter = defaultFileAdapter
380
382
  * @param langCode Optional language code to narrow selection (case-insensitive)
381
383
  * @returns History entries for that user/language, or empty array if none
382
384
  */
383
- export function readGrid3HistoryForUser(userName, langCode) {
384
- const dbPath = findGrid3UserHistory(userName, langCode);
385
+ export async function readGrid3HistoryForUser(userName, langCode) {
386
+ const dbPath = await findGrid3UserHistory(userName, langCode);
385
387
  if (!dbPath)
386
388
  return [];
387
- return readGrid3History(dbPath);
389
+ return await readGrid3History(dbPath);
388
390
  }
389
391
  /**
390
392
  * Load all available Grid 3 histories on the machine.
391
393
  * @returns Combined history entries from every discovered history.sqlite
392
394
  */
393
- export function readAllGrid3History() {
394
- const paths = findGrid3HistoryDatabases();
395
- return paths.flatMap((p) => readGrid3History(p));
395
+ export async function readAllGrid3History() {
396
+ const paths = await findGrid3HistoryDatabases();
397
+ const history = await Promise.all(paths.map(async (p) => await readGrid3History(p)));
398
+ return history.flat();
396
399
  }
@@ -277,13 +277,13 @@ function parseSymbolReferenceSafe(reference) {
277
277
  /**
278
278
  * Export symbol references to CSV for manual extraction
279
279
  */
280
- export function exportSymbolReferencesToCsv(report, outputPath, fileAdapter = defaultFileAdapter) {
280
+ export async function exportSymbolReferencesToCsv(report, outputPath, fileAdapter = defaultFileAdapter) {
281
281
  const { writeTextToPath } = fileAdapter;
282
282
  const lines = ['Reference,Library,Path,Attribution,License'];
283
283
  for (const symbol of report.missingSymbols) {
284
284
  lines.push(`"${symbol.reference}","${symbol.library}","${symbol.path}","${symbol.attribution || ''}","${symbol.license || ''}"`);
285
285
  }
286
- writeTextToPath(outputPath, lines.join('\n'));
286
+ await writeTextToPath(outputPath, lines.join('\n'));
287
287
  }
288
288
  export function createSymbolManifest(tree, gridsetName) {
289
289
  const manifest = {
@@ -14,9 +14,9 @@ import { defaultFileAdapter } from '../../utils/io';
14
14
  * @param pixFilePath - Path to .pix file
15
15
  * @returns Search index
16
16
  */
17
- export function parsePixFile(pixFilePath, fileAdapter = defaultFileAdapter) {
17
+ export async function parsePixFile(pixFilePath, fileAdapter = defaultFileAdapter) {
18
18
  const { readTextFromInput, basename } = fileAdapter;
19
- const content = readTextFromInput(pixFilePath);
19
+ const content = await readTextFromInput(pixFilePath);
20
20
  const library = basename(pixFilePath, '.pix');
21
21
  const searchTerms = new Map();
22
22
  const filenames = new Map();
@@ -43,18 +43,18 @@ export function parsePixFile(pixFilePath, fileAdapter = defaultFileAdapter) {
43
43
  * @param options - Search options
44
44
  * @returns Map of library name to search index
45
45
  */
46
- export function loadSearchIndexes(options = {}, fileAdapter = defaultFileAdapter) {
46
+ export async function loadSearchIndexes(options = {}, fileAdapter = defaultFileAdapter) {
47
47
  const { listDir, pathExists, join, basename } = fileAdapter;
48
48
  const { grid3Path, locale = 'en-GB', libraries: specifiedLibs } = options;
49
49
  if (!grid3Path) {
50
50
  throw new Error('grid3Path is required for symbol search');
51
51
  }
52
52
  const searchIndexesDir = join(grid3Path, 'Locale', locale, 'symbolsearch');
53
- if (!pathExists(searchIndexesDir)) {
53
+ if (!(await pathExists(searchIndexesDir))) {
54
54
  throw new Error(`Symbol search directory not found: ${searchIndexesDir}`);
55
55
  }
56
56
  const indexes = new Map();
57
- const files = listDir(searchIndexesDir);
57
+ const files = await listDir(searchIndexesDir);
58
58
  for (const file of files) {
59
59
  if (!file.endsWith('.pix')) {
60
60
  continue;
@@ -68,7 +68,7 @@ export function loadSearchIndexes(options = {}, fileAdapter = defaultFileAdapter
68
68
  }
69
69
  try {
70
70
  const pixFilePath = join(searchIndexesDir, file);
71
- const index = parsePixFile(pixFilePath);
71
+ const index = await parsePixFile(pixFilePath);
72
72
  indexes.set(libraryName, index);
73
73
  }
74
74
  catch (error) {
@@ -83,8 +83,8 @@ export function loadSearchIndexes(options = {}, fileAdapter = defaultFileAdapter
83
83
  * @param options - Search options
84
84
  * @returns Array of search results
85
85
  */
86
- export function searchSymbols(searchTerm, options = {}) {
87
- const indexes = loadSearchIndexes(options);
86
+ export async function searchSymbols(searchTerm, options = {}) {
87
+ const indexes = await loadSearchIndexes(options);
88
88
  const results = [];
89
89
  const lowerSearchTerm = searchTerm.toLowerCase().trim();
90
90
  const limit = options.limit || 100;
@@ -137,8 +137,8 @@ export function searchSymbols(searchTerm, options = {}) {
137
137
  * @param options - Search options
138
138
  * @returns Symbol filename or undefined
139
139
  */
140
- export function getSymbolFilename(searchTerm, library, options = {}) {
141
- const indexes = loadSearchIndexes({
140
+ export async function getSymbolFilename(searchTerm, library, options = {}) {
141
+ const indexes = await loadSearchIndexes({
142
142
  ...options,
143
143
  libraries: [library],
144
144
  });
@@ -155,8 +155,8 @@ export function getSymbolFilename(searchTerm, library, options = {}) {
155
155
  * @param options - Search options
156
156
  * @returns Display name or undefined
157
157
  */
158
- export function getSymbolDisplayName(symbolFilename, library, options = {}) {
159
- const indexes = loadSearchIndexes({
158
+ export async function getSymbolDisplayName(symbolFilename, library, options = {}) {
159
+ const indexes = await loadSearchIndexes({
160
160
  ...options,
161
161
  libraries: [library],
162
162
  });
@@ -172,8 +172,8 @@ export function getSymbolDisplayName(symbolFilename, library, options = {}) {
172
172
  * @param options - Search options
173
173
  * @returns Array of search terms
174
174
  */
175
- export function getAllSearchTerms(library, options = {}) {
176
- const indexes = loadSearchIndexes({
175
+ export async function getAllSearchTerms(library, options = {}) {
176
+ const indexes = await loadSearchIndexes({
177
177
  ...options,
178
178
  libraries: [library],
179
179
  });
@@ -189,8 +189,8 @@ export function getAllSearchTerms(library, options = {}) {
189
189
  * @param options - Search options
190
190
  * @returns Array of suggested terms
191
191
  */
192
- export function getSearchSuggestions(partialTerm, options = {}) {
193
- const indexes = loadSearchIndexes(options);
192
+ export async function getSearchSuggestions(partialTerm, options = {}) {
193
+ const indexes = await loadSearchIndexes(options);
194
194
  const suggestions = new Set();
195
195
  const lowerPartial = partialTerm.toLowerCase().trim();
196
196
  for (const index of indexes.values()) {
@@ -208,8 +208,8 @@ export function getSearchSuggestions(partialTerm, options = {}) {
208
208
  * @param options - Search options
209
209
  * @returns Array of full symbol references
210
210
  */
211
- export function searchSymbolsWithReferences(searchTerm, options = {}) {
212
- const results = searchSymbols(searchTerm, options);
211
+ export async function searchSymbolsWithReferences(searchTerm, options = {}) {
212
+ const results = await searchSymbols(searchTerm, options);
213
213
  return results.map((r) => `[${r.library}]${r.symbolFilename}`);
214
214
  }
215
215
  /**
@@ -217,8 +217,8 @@ export function searchSymbolsWithReferences(searchTerm, options = {}) {
217
217
  * @param options - Search options
218
218
  * @returns Map of library name to symbol count
219
219
  */
220
- export function countLibrarySymbols(options = {}) {
221
- const indexes = loadSearchIndexes(options);
220
+ export async function countLibrarySymbols(options = {}) {
221
+ const indexes = await loadSearchIndexes(options);
222
222
  const counts = new Map();
223
223
  for (const [libraryName, index] of indexes.entries()) {
224
224
  counts.set(libraryName, index.searchTerms.size);
@@ -230,8 +230,8 @@ export function countLibrarySymbols(options = {}) {
230
230
  * @param options - Search options
231
231
  * @returns Statistics about available symbols
232
232
  */
233
- export function getSymbolSearchStats(options = {}) {
234
- const indexes = loadSearchIndexes(options);
233
+ export async function getSymbolSearchStats(options = {}) {
234
+ const indexes = await loadSearchIndexes(options);
235
235
  const stats = {
236
236
  totalLibraries: indexes.size,
237
237
  totalSymbols: 0,