@willwade/aac-processors 0.1.3 → 0.1.4

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.
@@ -12,11 +12,15 @@
12
12
  * @returns Decrypted and inflated buffer
13
13
  */
14
14
  export function decryptGridsetEntry(buffer, password) {
15
+ const nodeRequire = typeof require === 'function' ? require : undefined;
16
+ if (!nodeRequire) {
17
+ throw new Error('Crypto utilities are not available in this environment.');
18
+ }
15
19
  // Dynamic require to avoid breaking in browser environments
16
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
17
- const crypto = require('crypto');
18
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
19
- const zlib = require('zlib');
20
+ const cryptoModule = 'crypto';
21
+ const zlibModule = 'zlib';
22
+ const crypto = nodeRequire(cryptoModule);
23
+ const zlib = nodeRequire(zlibModule);
20
24
  const pwd = (password || 'Chocolate').padEnd(32, ' ');
21
25
  const key = Buffer.from(pwd.slice(0, 32), 'utf8');
22
26
  const iv = Buffer.from(pwd.slice(0, 16), 'utf8');
@@ -41,10 +45,13 @@ export function decryptGridsetEntry(buffer, password) {
41
45
  */
42
46
  export function isCryptoAvailable() {
43
47
  try {
44
- // eslint-disable-next-line @typescript-eslint/no-var-requires
45
- require('crypto');
46
- // eslint-disable-next-line @typescript-eslint/no-var-requires
47
- require('zlib');
48
+ const nodeRequire = typeof require === 'function' ? require : undefined;
49
+ if (!nodeRequire)
50
+ return false;
51
+ const cryptoModule = 'crypto';
52
+ const zlibModule = 'zlib';
53
+ nodeRequire(cryptoModule);
54
+ nodeRequire(zlibModule);
48
55
  return true;
49
56
  }
50
57
  catch {
@@ -13,7 +13,7 @@ import { parseSymbolReference } from './gridset/symbols';
13
13
  import { isSymbolLibraryReference } from './gridset/resolver';
14
14
  import { generateCloneId } from '../utilities/analytics/utils/idGenerator';
15
15
  import { translateWithSymbols, extractSymbolsFromButton } from './gridset/symbolAlignment';
16
- import { readBinaryFromInput, decodeText } from '../utils/io';
16
+ import { readBinaryFromInput, decodeText, writeBinaryToPath } from '../utils/io';
17
17
  let JSZipModule;
18
18
  async function getJSZip() {
19
19
  if (!JSZipModule) {
@@ -1615,8 +1615,7 @@ class GridsetProcessor extends BaseProcessor {
1615
1615
  if (Object.keys(tree.pages).length === 0) {
1616
1616
  // Create empty zip for empty tree
1617
1617
  const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
1618
- // eslint-disable-next-line @typescript-eslint/no-var-requires
1619
- require('fs').writeFileSync(outputPath, zipBuffer);
1618
+ writeBinaryToPath(outputPath, zipBuffer);
1620
1619
  return;
1621
1620
  }
1622
1621
  // Collect all unique styles from pages and buttons
@@ -1911,8 +1910,7 @@ class GridsetProcessor extends BaseProcessor {
1911
1910
  zip.file('FileMap.xml', fileMapXmlContent, { binary: false });
1912
1911
  // Write the zip file
1913
1912
  const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
1914
- // eslint-disable-next-line @typescript-eslint/no-var-requires
1915
- require('fs').writeFileSync(outputPath, zipBuffer);
1913
+ writeBinaryToPath(outputPath, zipBuffer);
1916
1914
  }
1917
1915
  // Helper method to calculate column definitions based on page layout
1918
1916
  calculateColumnDefinitions(page) {
@@ -146,11 +146,15 @@ class ObfProcessor extends BaseProcessor {
146
146
  async processBoard(boardData, _boardPath) {
147
147
  const sourceButtons = boardData.buttons || [];
148
148
  // Calculate page ID first (used to make button IDs unique)
149
- const pageId = _boardPath && _boardPath.endsWith('.obf') && !_boardPath.includes('/')
149
+ const isZipEntry = _boardPath &&
150
+ _boardPath.endsWith('.obf') &&
151
+ !_boardPath.includes('/') &&
152
+ !_boardPath.includes('\\');
153
+ const pageId = isZipEntry
150
154
  ? _boardPath // Zip entry - use filename to match navigation paths
151
155
  : boardData?.id
152
156
  ? String(boardData.id)
153
- : _boardPath?.split('/').pop() || '';
157
+ : _boardPath?.split(/[/\\]/).pop() || '';
154
158
  const buttons = await Promise.all(sourceButtons.map(async (btn) => {
155
159
  const semanticAction = btn.load_board
156
160
  ? {
@@ -1,10 +1,30 @@
1
1
  let cachedFs = null;
2
2
  let cachedPath = null;
3
+ let cachedRequire = undefined;
4
+ function getNodeRequire() {
5
+ if (cachedRequire === undefined) {
6
+ if (typeof require === 'function') {
7
+ cachedRequire = require;
8
+ }
9
+ else if (typeof globalThis !== 'undefined') {
10
+ const maybeRequire = globalThis.require;
11
+ cachedRequire = typeof maybeRequire === 'function' ? maybeRequire : null;
12
+ }
13
+ else {
14
+ cachedRequire = null;
15
+ }
16
+ }
17
+ if (!cachedRequire) {
18
+ throw new Error('File system access is not available in this environment.');
19
+ }
20
+ return cachedRequire;
21
+ }
3
22
  export function getFs() {
4
23
  if (!cachedFs) {
5
24
  try {
6
- // eslint-disable-next-line @typescript-eslint/no-var-requires
7
- cachedFs = require('fs');
25
+ const nodeRequire = getNodeRequire();
26
+ const fsModule = 'fs';
27
+ cachedFs = nodeRequire(fsModule);
8
28
  }
9
29
  catch {
10
30
  throw new Error('File system access is not available in this environment.');
@@ -18,8 +38,9 @@ export function getFs() {
18
38
  export function getPath() {
19
39
  if (!cachedPath) {
20
40
  try {
21
- // eslint-disable-next-line @typescript-eslint/no-var-requires
22
- cachedPath = require('path');
41
+ const nodeRequire = getNodeRequire();
42
+ const pathModule = 'path';
43
+ cachedPath = nodeRequire(pathModule);
23
44
  }
24
45
  catch {
25
46
  throw new Error('Path utilities are not available in this environment.');
package/dist/cli/index.js CHANGED
@@ -7,6 +7,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const commander_1 = require("commander");
8
8
  const prettyPrint_1 = require("./prettyPrint");
9
9
  const analyze_1 = require("../core/analyze");
10
+ const history_1 = require("../utilities/analytics/history");
11
+ const analytics_1 = require("../utilities/analytics");
12
+ const aac_1 = require("../types/aac");
10
13
  const path_1 = __importDefault(require("path"));
11
14
  const fs_1 = __importDefault(require("fs"));
12
15
  // Helper function to detect format from file/folder path
@@ -276,6 +279,164 @@ commander_1.program
276
279
  process.exit(1);
277
280
  }
278
281
  });
282
+ commander_1.program
283
+ .command('history <input>')
284
+ .option('--format <format>', 'Output format: raw or baton', 'raw')
285
+ .option('--out <path>', 'Write output to a file instead of stdout')
286
+ .option('--source <source>', 'History source: auto, grid3, snap', 'auto')
287
+ .option('--anonymous-uuid <uuid>', 'Anonymous UUID for baton export')
288
+ .option('--export-date <iso>', 'Export date for baton export (ISO string)')
289
+ .option('--encryption <mode>', 'Encryption label for baton export', 'none')
290
+ .option('--version <version>', 'Baton export version', '1.0')
291
+ .action((input, options) => {
292
+ try {
293
+ if (!fs_1.default.existsSync(input)) {
294
+ throw new Error(`File not found: ${input}`);
295
+ }
296
+ const normalizedSource = (options.source || 'auto').toLowerCase();
297
+ const ext = path_1.default.extname(input).toLowerCase();
298
+ const isGrid3Db = ext === '.sqlite' || path_1.default.basename(input).toLowerCase() === 'history.sqlite';
299
+ const isSnap = ext === '.sps' || ext === '.spb';
300
+ let entries;
301
+ if (normalizedSource === 'grid3' || (normalizedSource === 'auto' && isGrid3Db)) {
302
+ entries = (0, history_1.readGrid3History)(input);
303
+ }
304
+ else if (normalizedSource === 'snap' || (normalizedSource === 'auto' && isSnap)) {
305
+ entries = (0, history_1.readSnapUsage)(input);
306
+ }
307
+ else {
308
+ throw new Error('Unable to detect history source. Use --source grid3 or --source snap.');
309
+ }
310
+ const format = (options.format || 'raw').toLowerCase();
311
+ let payload = entries;
312
+ if (format === 'baton') {
313
+ payload = (0, history_1.exportHistoryToBaton)(entries, {
314
+ version: options.version,
315
+ exportDate: options.exportDate,
316
+ encryption: options.encryption,
317
+ anonymousUUID: options.anonymousUuid,
318
+ });
319
+ }
320
+ else if (format !== 'raw') {
321
+ throw new Error(`Unsupported format: ${format}`);
322
+ }
323
+ const output = JSON.stringify(payload, null, 2);
324
+ if (options.out) {
325
+ fs_1.default.writeFileSync(options.out, output);
326
+ }
327
+ else {
328
+ console.log(output);
329
+ }
330
+ }
331
+ catch (error) {
332
+ console.error('Error exporting history:', error instanceof Error ? error.message : String(error));
333
+ process.exit(1);
334
+ }
335
+ });
336
+ commander_1.program
337
+ .command('metrics <file>')
338
+ .option('--format <format>', 'Format type (auto-detected if not specified)')
339
+ .option('--pretty', 'Pretty print JSON output')
340
+ .option('--out <path>', 'Write output to a file instead of stdout')
341
+ .option('--preserve-all-buttons', 'Preserve all buttons including navigation/system buttons')
342
+ .option('--no-exclude-navigation', "Don't exclude navigation buttons (Home, Back)")
343
+ .option('--no-exclude-system', "Don't exclude system buttons (Delete, Clear, etc.)")
344
+ .option('--exclude-buttons <list>', 'Comma-separated list of button labels/terms to exclude')
345
+ .option('--gridset-password <password>', 'Password for encrypted Grid3 archives (.gridsetx)')
346
+ .option('--access-method <method>', 'direct or scanning', 'direct')
347
+ .option('--scanning-pattern <pattern>', 'linear, row-column, or block', 'row-column')
348
+ .option('--selection-method <method>', 'auto-1-switch, step-1-switch, or step-2-switch', 'auto-1-switch')
349
+ .option('--error-correction', 'Enable scanning error correction', false)
350
+ .option('--use-prediction', 'Enable prediction in CARE scoring', false)
351
+ .option('--no-smart-grammar', 'Disable smart grammar word forms')
352
+ .option('--care', 'Include CARE comparison output', false)
353
+ .action(async (file, options) => {
354
+ try {
355
+ const filteringOptions = parseFilteringOptions(options);
356
+ const format = options.format || detectFormat(file);
357
+ const processor = (0, analyze_1.getProcessor)(format, filteringOptions);
358
+ const tree = await processor.loadIntoTree(file);
359
+ const accessMethod = (options.accessMethod || 'direct').toLowerCase();
360
+ const scanningPattern = (options.scanningPattern || 'row-column').toLowerCase();
361
+ const selectionMethodParam = (options.selectionMethod || 'auto-1-switch').toLowerCase();
362
+ const errorCorrection = !!options.errorCorrection;
363
+ let scanningConfig = undefined;
364
+ if (accessMethod === 'scanning') {
365
+ let cellScanningOrder = aac_1.CellScanningOrder.SimpleScan;
366
+ let blockScanEnabled = false;
367
+ switch (scanningPattern) {
368
+ case 'linear':
369
+ cellScanningOrder = aac_1.CellScanningOrder.SimpleScan;
370
+ break;
371
+ case 'row-column':
372
+ cellScanningOrder = aac_1.CellScanningOrder.RowColumnScan;
373
+ break;
374
+ case 'block':
375
+ cellScanningOrder = aac_1.CellScanningOrder.RowColumnScan;
376
+ blockScanEnabled = true;
377
+ break;
378
+ default:
379
+ throw new Error(`Unsupported scanning pattern: ${scanningPattern}`);
380
+ }
381
+ let selectionMethod = aac_1.ScanningSelectionMethod.AutoScan;
382
+ switch (selectionMethodParam) {
383
+ case 'auto-1-switch':
384
+ selectionMethod = aac_1.ScanningSelectionMethod.AutoScan;
385
+ break;
386
+ case 'step-1-switch':
387
+ selectionMethod = aac_1.ScanningSelectionMethod.StepScan1Switch;
388
+ break;
389
+ case 'step-2-switch':
390
+ selectionMethod = aac_1.ScanningSelectionMethod.StepScan2Switch;
391
+ break;
392
+ default:
393
+ throw new Error(`Unsupported selection method: ${selectionMethodParam}`);
394
+ }
395
+ scanningConfig = {
396
+ cellScanningOrder,
397
+ blockScanEnabled,
398
+ selectionMethod,
399
+ errorCorrectionEnabled: errorCorrection,
400
+ errorRate: errorCorrection ? 0.1 : undefined,
401
+ };
402
+ }
403
+ const calculator = new analytics_1.MetricsCalculator();
404
+ const metrics = calculator.analyze(tree, {
405
+ scanningConfig,
406
+ useSmartGrammar: options.smartGrammar,
407
+ });
408
+ let care = undefined;
409
+ if (options.care) {
410
+ const comparison = new analytics_1.ComparisonAnalyzer();
411
+ care = comparison.compare(metrics, metrics, {
412
+ includeSentences: true,
413
+ usePrediction: !!options.usePrediction,
414
+ scanningConfig,
415
+ });
416
+ }
417
+ const result = {
418
+ format,
419
+ filtering: filteringOptions,
420
+ accessMethod,
421
+ scanningPattern,
422
+ selectionMethod: selectionMethodParam,
423
+ errorCorrection,
424
+ metrics,
425
+ care,
426
+ };
427
+ const output = options.pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result);
428
+ if (options.out) {
429
+ fs_1.default.writeFileSync(options.out, output);
430
+ }
431
+ else {
432
+ console.log(output);
433
+ }
434
+ }
435
+ catch (error) {
436
+ console.error('Error calculating metrics:', error instanceof Error ? error.message : String(error));
437
+ process.exit(1);
438
+ }
439
+ });
279
440
  // Show help if no command provided
280
441
  if (process.argv.length <= 2) {
281
442
  commander_1.program.help();
@@ -16,11 +16,15 @@ exports.isCryptoAvailable = isCryptoAvailable;
16
16
  * @returns Decrypted and inflated buffer
17
17
  */
18
18
  function decryptGridsetEntry(buffer, password) {
19
+ const nodeRequire = typeof require === 'function' ? require : undefined;
20
+ if (!nodeRequire) {
21
+ throw new Error('Crypto utilities are not available in this environment.');
22
+ }
19
23
  // Dynamic require to avoid breaking in browser environments
20
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
21
- const crypto = require('crypto');
22
- // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-return
23
- const zlib = require('zlib');
24
+ const cryptoModule = 'crypto';
25
+ const zlibModule = 'zlib';
26
+ const crypto = nodeRequire(cryptoModule);
27
+ const zlib = nodeRequire(zlibModule);
24
28
  const pwd = (password || 'Chocolate').padEnd(32, ' ');
25
29
  const key = Buffer.from(pwd.slice(0, 32), 'utf8');
26
30
  const iv = Buffer.from(pwd.slice(0, 16), 'utf8');
@@ -45,10 +49,13 @@ function decryptGridsetEntry(buffer, password) {
45
49
  */
46
50
  function isCryptoAvailable() {
47
51
  try {
48
- // eslint-disable-next-line @typescript-eslint/no-var-requires
49
- require('crypto');
50
- // eslint-disable-next-line @typescript-eslint/no-var-requires
51
- require('zlib');
52
+ const nodeRequire = typeof require === 'function' ? require : undefined;
53
+ if (!nodeRequire)
54
+ return false;
55
+ const cryptoModule = 'crypto';
56
+ const zlibModule = 'zlib';
57
+ nodeRequire(cryptoModule);
58
+ nodeRequire(zlibModule);
52
59
  return true;
53
60
  }
54
61
  catch {
@@ -1641,8 +1641,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1641
1641
  if (Object.keys(tree.pages).length === 0) {
1642
1642
  // Create empty zip for empty tree
1643
1643
  const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
1644
- // eslint-disable-next-line @typescript-eslint/no-var-requires
1645
- require('fs').writeFileSync(outputPath, zipBuffer);
1644
+ (0, io_1.writeBinaryToPath)(outputPath, zipBuffer);
1646
1645
  return;
1647
1646
  }
1648
1647
  // Collect all unique styles from pages and buttons
@@ -1937,8 +1936,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
1937
1936
  zip.file('FileMap.xml', fileMapXmlContent, { binary: false });
1938
1937
  // Write the zip file
1939
1938
  const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
1940
- // eslint-disable-next-line @typescript-eslint/no-var-requires
1941
- require('fs').writeFileSync(outputPath, zipBuffer);
1939
+ (0, io_1.writeBinaryToPath)(outputPath, zipBuffer);
1942
1940
  }
1943
1941
  // Helper method to calculate column definitions based on page layout
1944
1942
  calculateColumnDefinitions(page) {
@@ -172,11 +172,15 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
172
172
  async processBoard(boardData, _boardPath) {
173
173
  const sourceButtons = boardData.buttons || [];
174
174
  // Calculate page ID first (used to make button IDs unique)
175
- const pageId = _boardPath && _boardPath.endsWith('.obf') && !_boardPath.includes('/')
175
+ const isZipEntry = _boardPath &&
176
+ _boardPath.endsWith('.obf') &&
177
+ !_boardPath.includes('/') &&
178
+ !_boardPath.includes('\\');
179
+ const pageId = isZipEntry
176
180
  ? _boardPath // Zip entry - use filename to match navigation paths
177
181
  : boardData?.id
178
182
  ? String(boardData.id)
179
- : _boardPath?.split('/').pop() || '';
183
+ : _boardPath?.split(/[/\\]/).pop() || '';
180
184
  const buttons = await Promise.all(sourceButtons.map(async (btn) => {
181
185
  const semanticAction = btn.load_board
182
186
  ? {
@@ -11,6 +11,7 @@ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const fs_1 = __importDefault(require("fs"));
13
13
  const crypto_1 = __importDefault(require("crypto"));
14
+ const os_1 = __importDefault(require("os"));
14
15
  const snapValidator_1 = require("../validation/snapValidator");
15
16
  /**
16
17
  * Map Snap Visible value to AAC standard visibility
@@ -55,11 +56,18 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
55
56
  async loadIntoTree(filePathOrBuffer) {
56
57
  await Promise.resolve();
57
58
  const tree = new treeStructure_1.AACTree();
58
- const filePath = typeof filePathOrBuffer === 'string'
59
- ? filePathOrBuffer
60
- : path_1.default.join(process.cwd(), 'temp.spb');
61
- if (Buffer.isBuffer(filePathOrBuffer)) {
62
- fs_1.default.writeFileSync(filePath, filePathOrBuffer);
59
+ let tempDir = null;
60
+ const filePath = typeof filePathOrBuffer !== 'string'
61
+ ? (() => {
62
+ tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'snap-'));
63
+ return path_1.default.join(tempDir, 'input.spb');
64
+ })()
65
+ : filePathOrBuffer;
66
+ if (typeof filePathOrBuffer !== 'string') {
67
+ const buffer = Buffer.isBuffer(filePathOrBuffer)
68
+ ? filePathOrBuffer
69
+ : Buffer.from(filePathOrBuffer);
70
+ fs_1.default.writeFileSync(filePath, buffer);
63
71
  }
64
72
  let db = null;
65
73
  try {
@@ -603,12 +611,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
603
611
  db.close();
604
612
  }
605
613
  // Clean up temporary file if created from buffer
606
- if (Buffer.isBuffer(filePathOrBuffer) && fs_1.default.existsSync(filePath)) {
614
+ if (tempDir && fs_1.default.existsSync(tempDir)) {
607
615
  try {
608
- fs_1.default.unlinkSync(filePath);
616
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
609
617
  }
610
618
  catch (e) {
611
- console.warn('Failed to clean up temporary file:', e);
619
+ console.warn('Failed to clean up temporary files:', e);
612
620
  }
613
621
  }
614
622
  }
@@ -36,6 +36,31 @@ export interface HistoryEntry {
36
36
  platform?: HistoryPlatformExtras;
37
37
  }
38
38
  export { dotNetTicksToDate };
39
+ export interface BatonExportMetadata {
40
+ timestamp: string;
41
+ latitude?: number | null;
42
+ longitude?: number | null;
43
+ }
44
+ export interface BatonExportSentence {
45
+ uuid: string;
46
+ anonymousUUID: string;
47
+ content: string;
48
+ metadata: BatonExportMetadata[];
49
+ source: HistorySource;
50
+ }
51
+ export interface BatonExport {
52
+ version: string;
53
+ exportDate: string;
54
+ encryption: string;
55
+ sentenceCount: number;
56
+ sentences: BatonExportSentence[];
57
+ }
58
+ export declare function exportHistoryToBaton(entries: HistoryEntry[], options?: {
59
+ version?: string;
60
+ exportDate?: string | Date;
61
+ encryption?: string;
62
+ anonymousUUID?: string;
63
+ }): BatonExport;
39
64
  /**
40
65
  * Read Grid 3 phrase history from a history.sqlite database and tag entries with their source.
41
66
  */
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dotNetTicksToDate = void 0;
4
+ exports.exportHistoryToBaton = exportHistoryToBaton;
4
5
  exports.readGrid3History = readGrid3History;
5
6
  exports.readGrid3HistoryForUser = readGrid3HistoryForUser;
6
7
  exports.readAllGrid3History = readAllGrid3History;
@@ -13,6 +14,61 @@ const dotnetTicks_1 = require("../../utils/dotnetTicks");
13
14
  Object.defineProperty(exports, "dotNetTicksToDate", { enumerable: true, get: function () { return dotnetTicks_1.dotNetTicksToDate; } });
14
15
  const helpers_1 = require("../../processors/gridset/helpers");
15
16
  const helpers_2 = require("../../processors/snap/helpers");
17
+ const generateUuid = () => {
18
+ if (typeof globalThis.crypto?.randomUUID === 'function') {
19
+ return globalThis.crypto.randomUUID();
20
+ }
21
+ // RFC4122-ish fallback for Node without crypto.randomUUID
22
+ const hex = '0123456789abcdef';
23
+ const bytes = Array.from({ length: 16 }, () => Math.floor(Math.random() * 256));
24
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
25
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
26
+ const toHex = (b) => hex[(b >> 4) & 0x0f] + hex[b & 0x0f];
27
+ return (toHex(bytes[0]) +
28
+ toHex(bytes[1]) +
29
+ toHex(bytes[2]) +
30
+ toHex(bytes[3]) +
31
+ '-' +
32
+ toHex(bytes[4]) +
33
+ toHex(bytes[5]) +
34
+ '-' +
35
+ toHex(bytes[6]) +
36
+ toHex(bytes[7]) +
37
+ '-' +
38
+ toHex(bytes[8]) +
39
+ toHex(bytes[9]) +
40
+ '-' +
41
+ toHex(bytes[10]) +
42
+ toHex(bytes[11]) +
43
+ toHex(bytes[12]) +
44
+ toHex(bytes[13]) +
45
+ toHex(bytes[14]) +
46
+ toHex(bytes[15]));
47
+ };
48
+ function exportHistoryToBaton(entries, options) {
49
+ const exportDate = options?.exportDate instanceof Date
50
+ ? options.exportDate.toISOString()
51
+ : options?.exportDate || new Date().toISOString();
52
+ const anonymousUUID = options?.anonymousUUID || generateUuid();
53
+ const sentences = entries.map((entry) => ({
54
+ uuid: generateUuid(),
55
+ anonymousUUID,
56
+ content: entry.content,
57
+ metadata: entry.occurrences.map((occ) => ({
58
+ timestamp: occ.timestamp.toISOString(),
59
+ latitude: occ.latitude ?? null,
60
+ longitude: occ.longitude ?? null,
61
+ })),
62
+ source: entry.source,
63
+ }));
64
+ return {
65
+ version: options?.version || '1.0',
66
+ exportDate,
67
+ encryption: options?.encryption || 'none',
68
+ sentenceCount: sentences.length,
69
+ sentences,
70
+ };
71
+ }
16
72
  /**
17
73
  * Read Grid 3 phrase history from a history.sqlite database and tag entries with their source.
18
74
  */
package/dist/utils/io.js CHANGED
@@ -12,11 +12,31 @@ exports.writeBinaryToPath = writeBinaryToPath;
12
12
  exports.writeTextToPath = writeTextToPath;
13
13
  let cachedFs = null;
14
14
  let cachedPath = null;
15
+ let cachedRequire = undefined;
16
+ function getNodeRequire() {
17
+ if (cachedRequire === undefined) {
18
+ if (typeof require === 'function') {
19
+ cachedRequire = require;
20
+ }
21
+ else if (typeof globalThis !== 'undefined') {
22
+ const maybeRequire = globalThis.require;
23
+ cachedRequire = typeof maybeRequire === 'function' ? maybeRequire : null;
24
+ }
25
+ else {
26
+ cachedRequire = null;
27
+ }
28
+ }
29
+ if (!cachedRequire) {
30
+ throw new Error('File system access is not available in this environment.');
31
+ }
32
+ return cachedRequire;
33
+ }
15
34
  function getFs() {
16
35
  if (!cachedFs) {
17
36
  try {
18
- // eslint-disable-next-line @typescript-eslint/no-var-requires
19
- cachedFs = require('fs');
37
+ const nodeRequire = getNodeRequire();
38
+ const fsModule = 'fs';
39
+ cachedFs = nodeRequire(fsModule);
20
40
  }
21
41
  catch {
22
42
  throw new Error('File system access is not available in this environment.');
@@ -30,8 +50,9 @@ function getFs() {
30
50
  function getPath() {
31
51
  if (!cachedPath) {
32
52
  try {
33
- // eslint-disable-next-line @typescript-eslint/no-var-requires
34
- cachedPath = require('path');
53
+ const nodeRequire = getNodeRequire();
54
+ const pathModule = 'path';
55
+ cachedPath = nodeRequire(pathModule);
35
56
  }
36
57
  catch {
37
58
  throw new Error('Path utilities are not available in this environment.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willwade/aac-processors",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
5
5
  "main": "dist/index.js",
6
6
  "browser": "dist/browser/index.browser.js",
@@ -88,7 +88,7 @@
88
88
  "build:all": "npm run build:node && npm run build:browser",
89
89
  "build:watch": "tsc -p tsconfig.node.json --watch",
90
90
  "clean": "rimraf dist coverage",
91
- "copy:assets": "cp -r src/utilities/analytics/reference/data dist/utilities/analytics/reference/",
91
+ "copy:assets": "node scripts/copy-assets.js",
92
92
  "lint": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\"",
93
93
  "lint:fix": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" --fix",
94
94
  "format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",